From f9e4f002b6c4a6ed34d6411e32c84c2fbbdf2b82 Mon Sep 17 00:00:00 2001 From: Derek Siemens Date: Mon, 2 Mar 2026 16:51:51 -0800 Subject: [PATCH 01/64] DS: Initial vibe coded approach for better error messages --- .../sqm-banking-info-form/formDefinitions.tsx | 40 +++ .../sqm-banking-info-form-view.tsx | 2 + .../sqm-banking-info-form.tsx | 245 ++++++++++++++++++ .../useBankingInfoForm.tsx | 133 +++++++++- 4 files changed, 418 insertions(+), 2 deletions(-) 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 e33adccd35..18779d10fe 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", }), })} > @@ -152,6 +164,8 @@ 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,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationLabel, + errorCode: errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > @@ -222,6 +240,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.taxPayerId?.type, label: props.text.taxPayerIdLabel, + errorCode: errors?.inputErrors?.taxPayerId?.errorCode, + fieldName: "taxPayerId", }), })} >, @@ -240,6 +260,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationCPFLabel, + errorCode: errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > @@ -262,6 +284,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.patronymicName?.type, label: props.text.patronymicNameLabel, + errorCode: errors?.inputErrors?.patronymicName?.errorCode, + fieldName: "patronymicName", }), })} > @@ -280,6 +304,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.voCode?.type, label: props.text.voCodeLabel, + errorCode: errors?.inputErrors?.voCode?.errorCode, + fieldName: "voCode", }), })} > @@ -299,6 +325,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.agencyCode?.type, label: props.text.agencyCodeLabel, + errorCode: errors?.inputErrors?.agencyCode?.errorCode, + fieldName: "agencyCode", }), })} > @@ -318,6 +346,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankAddress?.type, label: props.text.bankAddressLabel, + errorCode: errors?.inputErrors?.bankAddress?.errorCode, + fieldName: "bankAddress", }), })} >, @@ -333,6 +363,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankCity?.type, label: props.text.bankCityLabel, + errorCode: errors?.inputErrors?.bankCity?.errorCode, + fieldName: "bankCity", }), })} >, @@ -348,6 +380,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankState?.type, label: props.text.bankStateLabel, + errorCode: errors?.inputErrors?.bankState?.errorCode, + fieldName: "bankState", }), })} >, @@ -363,6 +397,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankPostalCode?.type, label: props.text.bankPostalCodeLabel, + errorCode: errors?.inputErrors?.bankPostalCode?.errorCode, + fieldName: "bankPostalCode", }), })} >, @@ -382,6 +418,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.branchCode?.type, label: props.text.branchCodeLabel, + errorCode: errors?.inputErrors?.branchCode?.errorCode, + fieldName: "branchCode", }), })} > @@ -400,6 +438,8 @@ export function getFormMap({ helpText: getValidationErrorMessage({ type: errors?.inputErrors?.beneficiaryClassification?.type, label: props.text.classificationLabel, + errorCode: errors?.inputErrors?.beneficiaryClassification?.errorCode, + fieldName: "beneficiaryClassification", }), })} > 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..3819882246 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; }; }; }; @@ -105,6 +106,7 @@ export interface BankingInfoFormViewProps { generalTitle: string; generalDescription: string; }; + errorMessages?: { [field: string]: string }; }; refs: { formRef: any; 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..8d005baa3e 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 @@ -350,6 +350,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 +564,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 +602,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( { @@ -525,6 +760,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.bankCountry?.type, label: props.text.bankLocationLabel, + errorCode: errors?.inputErrors?.bankCountry?.errorCode, + fieldName: "bankCountry", }), })} > @@ -578,6 +815,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.paymentThreshold?.type, label: props.text.paymentThresholdSelectLabel, + errorCode: errors?.inputErrors?.paymentThreshold?.errorCode, + fieldName: "paymentThreshold", }), })} > @@ -601,6 +840,8 @@ export class BankingInfoForm { helpText: getValidationErrorMessage({ type: errors?.inputErrors?.paymentDay?.type, label: props.text.paymentDaySelectLabel, + errorCode: errors?.inputErrors?.paymentDay?.errorCode, + fieldName: "paymentDay", }), })} > @@ -629,6 +870,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", }), })} > 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..7b84895eed 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 @@ -49,6 +49,131 @@ 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. + * + * The Impact API returns UpperCamelCase field names (e.g. `BankAccountNumber`). + * The GraphQL layer in `UserServiceImpl.java` converts these to lowerCamelCase + * via `CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, errorDto.field)`. + * + * Most converted field names already match the form field names exactly + * (e.g. `bankAccountNumber`, `swiftCode`, `routingCode`). Only entries + * where the GraphQL field name differs from the form field name need + * to be listed here. + */ +const API_FIELD_TO_FORM_FIELD: Record = { + // bankProvinceState → form uses bankState + bankProvinceState: "bankState", +}; + +/** + * Maps Impact API error messages (from validationErrors[].message) to short, + * readable frontend error codes used in the ICU select props. + * + * The Impact API returns human-readable English messages (e.g. "Invalid Routing Code"). + * The GraphQL layer passes these through unchanged. The keys below match those messages + * to short frontend codes used in the ICU `{errorCode, select, ...}` props. + * + * If a message doesn't match any key here, it passes through as-is to the ICU select's + * `other` branch, which displays it directly via `{errorCode}`. + */ +const API_MESSAGE_TO_FRONTEND: Record = { + // Beneficiary account name + "Beneficiary name is empty": "empty", + "Contains invalid characters": "invalidCharacters", + "Name is purely numeric": "numeric", + "Name exceeds 70 characters": "tooLong", + "CNY currency account with non-English characters": "nonEnglish", + "Beneficiary name does not match tax document": "nameMismatch", + "Business beneficiary name does not match tax document": + "businessNameMismatch", + "Payee name does not match tax document": "payeeMismatch", + "Business payee name does not match tax document": "businessPayeeMismatch", + + // Bank account number + "Account number is empty": "empty", + "Account number invalid & country is UK": "invalidUk", + "Account number invalid & country is not UK": "invalid", + + // IBAN + "IBAN is empty": "ibanEmpty", + "IBAN contains non-alphanumeric chars": "ibanAlphanumeric", + "IBAN fails obfuscation or IBAN validation": "ibanInvalid", + 'UK GBP account but IBAN doesn\'t start with "GB"': "ibanCountryMismatch", + + // Routing code + "Empty or invalid routing code & country is Australia": "invalidBsb", + "Empty or invalid routing code & country is UK": "invalidSortCode", + "Empty routing code (other countries)": "empty", + "Invalid routing code (other countries)": "invalid", + + // SWIFT / BIC + "SWIFT/BIC code is empty": "empty", + "SWIFT/BIC contains non-alphanumeric chars": "alphanumeric", + "SWIFT/BIC fails obfuscation or BIC validation": "invalid", + + // Bank account type + "Account type is null": "empty", + + // Bank name + "Bank name is empty": "empty", + + // Tax payer ID + "Tax payer ID is empty (generic)": "empty", + "Tax payer ID is empty (Argentina)": "emptyAr", + "Tax payer ID is empty (South Korea)": "emptyKr", + "Tax payer ID not alphanumeric (generic)": "alphanumeric", + "Tax payer ID not alphanumeric (Argentina)": "alphanumericAr", + "Tax payer ID not alphanumeric (South Korea)": "alphanumericKr", + "Argentina ID not 11 chars, or South Korea individual ID not 13 / business ID not 10 chars": + "invalid", + "Argentina-specific invalid length": "invalidAr", + "South Korea-specific invalid length": "invalidKr", + "KZT currency and tax payer ID not 12 chars": "invalidKzt", + "Classification is CNPJ and tax payer ID length < 14": "cnpjTooShort", + "Classification is CPF and tax payer ID length < 11": "cpfTooShort", + + // Patronymic name + "Individual classification & patronymic name is empty": "empty", + "Patronymic name is not alphanumeric": "alphanumeric", + + // VO code + "VO code is empty": "empty", + "VO code is not alphanumeric": "alphanumeric", + + // Agency code + "Agency code is empty": "empty", + "Agency code is not alphanumeric": "alphanumeric", + "Agency code length < 5": "tooShort", + + // Bank address fields + "Bank address is empty": "empty", + "Bank city is empty": "empty", + "Bank province/state is empty": "empty", + "Bank postal code is empty": "empty", + + // Branch code / name + "Branch code invalid": "invalid", + "Branch name is empty": "empty", + + // Classification code + "Classification code is empty (non-KZT)": "empty", + "Empty or not exactly 2 chars (KZT currency)": "invalidKzt", + + // PayPal + "PayPal email is empty": "empty", + "Currency not eligible for PayPal": "unsupportedCurrency", + "Email format is invalid": "invalidEmail", + "PayPal OAuth state is not SUCCESS": "verificationIncomplete", + + // Payment schedule + "Balance threshold schedule with zero threshold": "empty", + "PayPal threshold not in allowed list, or bank threshold is invalid": + "invalid", + "Fixed-day schedule without a specific day": "empty", + "Fixed-day schedule with day not in [1, 15]": "invalid", +}; + export type BankingInfoFormData = { // Fields that are auto-filled bankCountry?: string; @@ -390,11 +515,15 @@ export function useBankingInfoForm( const mappedValidationErrors = validationErrors?.reduce( (agg, error) => { + const formField = + API_FIELD_TO_FORM_FIELD[error.field] || error.field; + const errorCode = + API_MESSAGE_TO_FRONTEND[error.message] || error.message; return { ...agg, - - [error.field]: { + [formField]: { type: "invalid", + errorCode, }, }; }, From 15a30f7b34ee2f03930816a0dd600a3568f26159 Mon Sep 17 00:00:00 2001 From: Derek Siemens Date: Thu, 5 Mar 2026 09:54:27 -0800 Subject: [PATCH 02/64] DS: update hooks for error path --- .../useBankingInfoForm.tsx | 152 +++++++++--------- 1 file changed, 80 insertions(+), 72 deletions(-) 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 7b84895eed..108c0c832e 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 @@ -67,111 +67,109 @@ const API_FIELD_TO_FORM_FIELD: Record = { }; /** - * Maps Impact API error messages (from validationErrors[].message) to short, + * Maps Impact API error code paths (from validationErrors[].errorPath) to short, * readable frontend error codes used in the ICU select props. * - * The Impact API returns human-readable English messages (e.g. "Invalid Routing Code"). - * The GraphQL layer passes these through unchanged. The keys below match those messages + * The Impact API returns a stable dot-delimited error path (e.g. + * "withdrawal.settings.error.routingcode") alongside the human-readable message. + * The GraphQL layer exposes this as `errorPath`. The keys below match those paths * to short frontend codes used in the ICU `{errorCode, select, ...}` props. - * - * If a message doesn't match any key here, it passes through as-is to the ICU select's - * `other` branch, which displays it directly via `{errorCode}`. */ -const API_MESSAGE_TO_FRONTEND: Record = { +const API_ERROR_PATH_TO_FRONTEND: Record = { // Beneficiary account name - "Beneficiary name is empty": "empty", - "Contains invalid characters": "invalidCharacters", - "Name is purely numeric": "numeric", - "Name exceeds 70 characters": "tooLong", - "CNY currency account with non-English characters": "nonEnglish", - "Beneficiary name does not match tax document": "nameMismatch", - "Business beneficiary name does not match tax document": + "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", - "Payee name does not match tax document": "payeeMismatch", - "Business payee name does not match tax document": "businessPayeeMismatch", + "withdrawal_settings.error.beneficiaryname_match": "nameMismatch", + "withdrawal_settings.error.business_checkpayeename_match": + "businessPayeeMismatch", + "withdrawal_settings.error.checkpayeename_match": "payeeMismatch", // Bank account number - "Account number is empty": "empty", - "Account number invalid & country is UK": "invalidUk", - "Account number invalid & country is not UK": "invalid", + "withdrawal.settings.error.accountnumber.empty": "empty", + "withdrawal.settings.error.accountnumber.uk": "invalidUk", + "withdrawal.settings.error.bankaccount.invalid": "invalid", // IBAN - "IBAN is empty": "ibanEmpty", - "IBAN contains non-alphanumeric chars": "ibanAlphanumeric", - "IBAN fails obfuscation or IBAN validation": "ibanInvalid", - 'UK GBP account but IBAN doesn\'t start with "GB"': "ibanCountryMismatch", + "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 - "Empty or invalid routing code & country is Australia": "invalidBsb", - "Empty or invalid routing code & country is UK": "invalidSortCode", - "Empty routing code (other countries)": "empty", - "Invalid routing code (other countries)": "invalid", + "withdrawal.settings.error.bsbNumber": "invalidBsb", + "withdrawal.settings.error.sortcode": "invalidSortCode", + "withdrawal.settings.error.routingNumber": "empty", + "withdrawal.settings.error.routingcode": "invalid", // SWIFT / BIC - "SWIFT/BIC code is empty": "empty", - "SWIFT/BIC contains non-alphanumeric chars": "alphanumeric", - "SWIFT/BIC fails obfuscation or BIC validation": "invalid", + "withdrawal.settings.error.bic": "empty", + "withdrawal.settings.error.bic.alphanumeric": "alphanumeric", + "withdrawal.settings.error.bic.invalid": "invalid", // Bank account type - "Account type is null": "empty", + "global.error.invalid.accounttype": "empty", // Bank name - "Bank name is empty": "empty", + "withdrawal.settings.error.bankName": "empty", // Tax payer ID - "Tax payer ID is empty (generic)": "empty", - "Tax payer ID is empty (Argentina)": "emptyAr", - "Tax payer ID is empty (South Korea)": "emptyKr", - "Tax payer ID not alphanumeric (generic)": "alphanumeric", - "Tax payer ID not alphanumeric (Argentina)": "alphanumericAr", - "Tax payer ID not alphanumeric (South Korea)": "alphanumericKr", - "Argentina ID not 11 chars, or South Korea individual ID not 13 / business ID not 10 chars": - "invalid", - "Argentina-specific invalid length": "invalidAr", - "South Korea-specific invalid length": "invalidKr", - "KZT currency and tax payer ID not 12 chars": "invalidKzt", - "Classification is CNPJ and tax payer ID length < 14": "cnpjTooShort", - "Classification is CPF and tax payer ID length < 11": "cpfTooShort", + "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 - "Individual classification & patronymic name is empty": "empty", - "Patronymic name is not alphanumeric": "alphanumeric", + "withdrawal.settings.error.patronymicName": "empty", + "withdrawal.settings.error.patronymicName.alphanumeric": "alphanumeric", // VO code - "VO code is empty": "empty", - "VO code is not alphanumeric": "alphanumeric", + "withdrawal.settings.error.voCode": "empty", + "withdrawal.settings.error.voCode.alphanumeric": "alphanumeric", // Agency code - "Agency code is empty": "empty", - "Agency code is not alphanumeric": "alphanumeric", - "Agency code length < 5": "tooShort", + "withdrawal.settings.error.agencyCode": "empty", + "withdrawal.settings.error.agencyCode.alphanumeric": "alphanumeric", + "withdrawal.settings.error.agencyCode.length": "tooShort", // Bank address fields - "Bank address is empty": "empty", - "Bank city is empty": "empty", - "Bank province/state is empty": "empty", - "Bank postal code is empty": "empty", + "withdrawal.settings.error.bankAddress": "empty", + "withdrawal.settings.error.bankCity": "empty", + "withdrawal.settings.error.bankProvinceState": "empty", + "withdrawal.settings.error.bankPostalCode": "empty", // Branch code / name - "Branch code invalid": "invalid", - "Branch name is empty": "empty", + "withdrawal.settings.error.branchCode": "invalid", + "withdrawal.settings.error.branchName": "empty", // Classification code - "Classification code is empty (non-KZT)": "empty", - "Empty or not exactly 2 chars (KZT currency)": "invalidKzt", + "withdrawal.settings.error.classificationCode.invalid": "empty", + "withdrawal.settings.error.classificationCode.invalid.kzt": "invalidKzt", // PayPal - "PayPal email is empty": "empty", - "Currency not eligible for PayPal": "unsupportedCurrency", - "Email format is invalid": "invalidEmail", - "PayPal OAuth state is not SUCCESS": "verificationIncomplete", + "payment.error.email": "empty", + "payment.error.paypal_not_supported": "unsupportedCurrency", + "payment.error.email.invalid": "invalidEmail", + "payment.error.paypal_verification_incomplete": "verificationIncomplete", // Payment schedule - "Balance threshold schedule with zero threshold": "empty", - "PayPal threshold not in allowed list, or bank threshold is invalid": - "invalid", - "Fixed-day schedule without a specific day": "empty", - "Fixed-day schedule with day not in [1, 15]": "invalid", + "payment.error.no_threshold": "empty", + "payment.error.invalid_threshold": "invalid", + "payment.error.no_dayOfMonth": "empty", + "payment.error.invalid_dayOfMonth": "invalid", }; export type BankingInfoFormData = { @@ -226,7 +224,11 @@ export function getFormInputs({ bitset, formMap }) { type SetImpactPublisherWithdrawalSettingsResult = { setImpactPublisherWithdrawalSettings: { success: boolean; - validationErrors: { field: string; message: string }[]; + validationErrors: { + field: string; + message: string; + errorPath: string; + }[]; }; }; type SetImpactPublisherWithdrawalSettingsInput = { @@ -245,7 +247,11 @@ type UpdateImpactPublisherWithdrawalSettingsInput = type UpdateImpactPublisherWithdrawalSettingsResult = { updateImpactPublisherWithdrawalSettings: { success: boolean; - validationErrors: { field: string; message: string }[]; + validationErrors: { + field: string; + message: string; + errorPath: string; + }[]; }; }; @@ -260,6 +266,7 @@ const SAVE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message + errorPath } } } @@ -276,6 +283,7 @@ const UPDATE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message + errorPath } } } @@ -518,7 +526,7 @@ export function useBankingInfoForm( const formField = API_FIELD_TO_FORM_FIELD[error.field] || error.field; const errorCode = - API_MESSAGE_TO_FRONTEND[error.message] || error.message; + API_ERROR_PATH_TO_FRONTEND[error.errorPath] || error.errorPath; return { ...agg, [formField]: { From 2fdaf6e7fd014d55789b1008996b4119ea77671d Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 6 Apr 2026 13:29:07 -0700 Subject: [PATCH 03/64] Add sqm-partner-info-modal --- packages/mint-components/src/components.d.ts | 128 ++++++++++++ .../PartnerInfoModal.stories.tsx | 171 +++++++++++++++ .../sqm-partner-info-modal-view.tsx | 193 +++++++++++++++++ .../sqm-partner-info-modal.tsx | 197 ++++++++++++++++++ .../sqm-stencilbook/sqm-stencilbook.tsx | 2 + 5 files changed, 691 insertions(+) create mode 100644 packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx create mode 100644 packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal-view.tsx create mode 100644 packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.tsx diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 0916004dd4..b2d0f14f48 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 { PartnerInfoModalViewProps } from "./components/sqm-partner-info-modal/sqm-partner-info-modal-view"; 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"; @@ -2190,6 +2191,65 @@ export namespace Components { */ "paginationText": string; } + interface SqmPartnerInfoModal { + /** + * @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; + /** + * @uiName Missing fields error text + * @uiWidget textArea + */ + "missingFieldsErrorText": string; + /** + * Header text when user has no existing partner + * @uiName New partner header + * @uiWidget textArea + */ + "modalBrandHeader": string; + /** + * @uiName Network error text + * @uiWidget textArea + */ + "networkErrorText": string; + /** + * @uiName Search country placeholder + */ + "searchCountryPlaceholder": string; + /** + * @uiName Search currency placeholder + */ + "searchCurrencyPlaceholder": string; + /** + * @uiName Submit button label + */ + "submitButtonLabel": string; + } interface SqmPasswordField { /** * @undocumented @@ -7377,6 +7437,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: { @@ -7901,6 +7967,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; @@ -10107,6 +10174,65 @@ declare namespace LocalJSX { */ "paginationText"?: string; } + interface SqmPartnerInfoModal { + /** + * @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; + /** + * @uiName Missing fields error text + * @uiWidget textArea + */ + "missingFieldsErrorText"?: string; + /** + * Header text when user has no existing partner + * @uiName New partner header + * @uiWidget textArea + */ + "modalBrandHeader"?: string; + /** + * @uiName Network error text + * @uiWidget textArea + */ + "networkErrorText"?: string; + /** + * @uiName Search country placeholder + */ + "searchCountryPlaceholder"?: string; + /** + * @uiName Search currency placeholder + */ + "searchCurrencyPlaceholder"?: string; + /** + * @uiName Submit button label + */ + "submitButtonLabel"?: string; + } interface SqmPasswordField { /** * @undocumented @@ -15023,6 +15149,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; @@ -15157,6 +15284,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-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..ca87628da2 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx @@ -0,0 +1,171 @@ +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), + onCountrySearch: (v: string) => console.log("Country search:", v), + onCurrencySearch: (v: string) => console.log("Currency search:", v), + onSubmit: () => console.log("Submit"), + onClose: () => console.log("Close"), +}; + +const defaultText = { + modalBrandHeader: "Welcome to {brandName} Program!", + descriptionNewPartner: + "We just need a bit more information about you before you start earning cash!", + descriptionExistingPartner: + "We noticed you are already an Impact.com partner, please confirm your information.", + countryLabel: "Country", + currencyLabel: "Currency", + submitButtonLabel: "Submit", + confirmButtonLabel: "Confirm", + searchCountryPlaceholder: "Search for a country", + searchCurrencyPlaceholder: "Search for a currency", +}; + +const defaultProps: PartnerInfoModalViewProps = { + states: { + open: true, + loading: false, + submitting: false, + isExistingPartner: false, + countryCode: "", + currency: "", + error: "", + success: false, + brandName: "Test Brand", + filteredCountries: demoCountries, + filteredCurrencies: demoCurrencies, + }, + 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 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 FullStackDemo = () => { + return ( + + ); +}; 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..9d2d768964 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal-view.tsx @@ -0,0 +1,193 @@ +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; + brandName: string; + filteredCountries: { countryCode: string; displayName: string }[]; + filteredCurrencies: { currencyCode: string; displayName: string }[]; + }; + callbacks: { + onCountryChange: (e: any) => void; + onCurrencyChange: (e: any) => void; + onCountrySearch: (value: string) => void; + onCurrencySearch: (value: string) => void; + onSubmit: () => void; + onClose: () => void; + }; + text: { + modalBrandHeader: string; + descriptionNewPartner: string; + descriptionExistingPartner: string; + countryLabel: string; + currencyLabel: string; + submitButtonLabel: string; + confirmButtonLabel: string; + searchCountryPlaceholder: string; + searchCurrencyPlaceholder: string; + }; +} + +const style = { + Dialog: { + "&::part(panel)": { + maxWidth: "480px", + }, + "&::part(title)": { + fontSize: "var(--sl-font-size-x-large)", + fontWeight: "600", + padding: + "var(--sl-spacing-x-large) var(--sl-spacing-x-large) 0 var(--sl-spacing-x-large)", + }, + "&::part(body)": { + padding: "var(--sl-spacing-small) var(--sl-spacing-x-large)", + fontSize: "var(--sl-font-size-small)", + overflow: "visible", + }, + "&::part(footer)": { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-small)", + padding: + "var(--sl-spacing-small) var(--sl-spacing-x-large) var(--sl-spacing-x-large)", + }, + "&::part(overlay)": { + background: "rgba(0, 0, 0, 0.5)", + }, + "&::part(close-button)": { + display: "none", + }, + }, + FormFields: { + display: "flex", + gap: "var(--sl-spacing-medium)", + marginTop: "var(--sl-spacing-medium)", + + "& > *": { + flex: 1, + }, + }, + ErrorMessage: { + color: "var(--sqm-danger-color-text, #d32f2f)", + fontSize: "var(--sl-font-size-small)", + marginTop: "var(--sl-spacing-x-small)", + }, + SearchInput: { + "&::part(base)": { + border: "none", + borderBottom: "1px solid var(--sl-color-neutral-300)", + borderRadius: "0", + }, + }, +}; + +export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { + const { states, callbacks, text } = props; + const sheet = createStyleSheet(style); + const styleString = sheet.toString(); + + const description = states.isExistingPartner + ? text.descriptionExistingPartner + : text.descriptionNewPartner; + + const buttonLabel = states.isExistingPartner + ? text.confirmButtonLabel + : text.submitButtonLabel; + + return ( + { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + // Prevent closing when clicking outside the dialog but not dropdowns + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + > + + +

{description}

+ +
+ + e.stopPropagation()} + onSl-input={(e: any) => + callbacks.onCountrySearch(e.target?.value || "") + } + /> + {states.filteredCountries?.map((c) => ( + {c.displayName} + ))} + + + + e.stopPropagation()} + onSl-input={(e: any) => + callbacks.onCurrencySearch(e.target?.value || "") + } + /> + {states.filteredCurrencies?.map((c) => ( + + {c.currencyCode} - {c.displayName} + + ))} + +
+ + {states.error &&

{states.error}

} + + + {buttonLabel} + +
+ ); +} 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..bb50d2d158 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/sqm-partner-info-modal.tsx @@ -0,0 +1,197 @@ +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 { getProps } from "../../utils/utils"; +import { + PartnerInfoModalView, + PartnerInfoModalViewProps, +} from "./sqm-partner-info-modal-view"; + +/** + * @uiName Partner Info Modal + * @exampleGroup Tax and Cash + * @validParents ["sqm-portal-container", "sqm-portal-frame", "div", "sqb-program-section", "sqb-conditional-section"] + * @example Partner Info Modal - + */ +@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() + modalBrandHeader: string = "Welcome to {brandName} Program!"; + + /** + * Description for new partner setup + * + * @uiName New partner description + * @uiWidget textArea + */ + @Prop() + descriptionNewPartner: string = + "We just need a bit more information about you before you start earning cash!"; + + /** + * Description for existing partner confirmation + * + * @uiName Existing partner description + * @uiWidget textArea + */ + @Prop() + descriptionExistingPartner: string = + "We noticed you are already an Impact.com partner, please confirm your information."; + + /** + * @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 select both a country and currency."; + + /** + * @undocumented + * @uiType object + */ + @Prop() demoData?: DemoData; + + constructor() { + withHooks(this); + } + disconnectedCallback() {} + + render() { + // AL: TODO add usePartnerInfoModal + const props = useDemoPartnerInfoModal(this); + + return ; + } +} + +function useDemoPartnerInfoModal( + props: PartnerInfoModal, +): PartnerInfoModalViewProps { + const [countryCode, setCountryCode] = useState("US"); + const [currency, setCurrency] = useState(""); + const [error, setError] = useState(""); + + const demoCountries = [ + { countryCode: "US", displayName: "United States" }, + { countryCode: "CA", displayName: "Canada" }, + { countryCode: "GB", displayName: "United Kingdom" }, + { countryCode: "AU", displayName: "Australia" }, + { countryCode: "DE", displayName: "Germany" }, + ]; + + 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" }, + ]; + + return deepmerge( + { + states: { + brandName: "Test Brand", + open: true, + loading: false, + submitting: false, + isExistingPartner: false, + countryCode, + currency, + error, + success: false, + filteredCountries: demoCountries, + filteredCurrencies: demoCurrencies, + }, + 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); + }, + onCountrySearch: () => {}, + onCurrencySearch: () => {}, + onSubmit: () => { + if (!countryCode || !currency) { + setError(props.missingFieldsErrorText); + return; + } + setError(""); + }, + onClose: () => {}, + }, + text: { + modalBrandHeader: props.modalBrandHeader, + descriptionNewPartner: props.descriptionNewPartner, + descriptionExistingPartner: props.descriptionExistingPartner, + countryLabel: props.countryLabel, + currencyLabel: props.currencyLabel, + submitButtonLabel: props.submitButtonLabel, + confirmButtonLabel: props.confirmButtonLabel, + searchCountryPlaceholder: props.searchCountryPlaceholder, + searchCurrencyPlaceholder: props.searchCurrencyPlaceholder, + }, + }, + props.demoData || {}, + { arrayMerge: (_, a) => a }, + ); +} 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 b52a78359a..ffe841f413 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx +++ b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx @@ -108,6 +108,7 @@ 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 { ShadowViewAddon } from "../../ShadowViewAddon"; import { CucumberAddon } from "./CucumberAddon"; @@ -221,6 +222,7 @@ const stories = [ TaxAndCashRewardsTable, TaxAndCashReferralTableRewardsCell, TaxAndCashReferralTable, + PartnerInfoModal, ]; /** From 2710f9cd8fe0ff549666ac9e81665ceb691a5e11 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Mon, 6 Apr 2026 14:08:12 -0700 Subject: [PATCH 04/64] publish pre-release --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 240 ++++++++++++++++++ .../sqm-banking-info-form/readme.md | 146 ++++++----- 3 files changed, 324 insertions(+), 64 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 4bdfc1be86..ac9c3990c4 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.1.7", + "version": "2.1.8-0", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 0916004dd4..74fc549d43 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -64,6 +64,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 */ @@ -73,18 +79,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 */ @@ -93,14 +123,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 */ @@ -109,14 +157,32 @@ 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; /** * One of three options listed for the classification field * @uiName Business classification option @@ -130,6 +196,12 @@ 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 entity field label */ @@ -237,6 +309,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 */ @@ -246,6 +324,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 @@ -281,11 +365,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 */ @@ -303,6 +405,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 */ @@ -316,6 +424,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 */ @@ -333,6 +447,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 */ @@ -7986,6 +8106,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 */ @@ -7995,18 +8121,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 */ @@ -8015,14 +8165,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 */ @@ -8031,14 +8199,32 @@ 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; /** * One of three options listed for the classification field * @uiName Business classification option @@ -8052,6 +8238,12 @@ 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 entity field label */ @@ -8159,6 +8351,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 */ @@ -8168,6 +8366,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 @@ -8203,11 +8407,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 */ @@ -8225,6 +8447,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 */ @@ -8238,6 +8466,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 */ @@ -8255,6 +8489,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 */ 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..55cc4173c3 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,89 @@ ## 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}}}"` | +| `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}}}"` | +| `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 From 561c2a745c05840f26b5ac52fcc2edebce2a647f Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Tue, 7 Apr 2026 08:55:07 -0700 Subject: [PATCH 05/64] cleaning up over-explained comments --- .../useBankingInfoForm.tsx | 44 +++++++------------ 1 file changed, 15 insertions(+), 29 deletions(-) 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 108c0c832e..497fb78815 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 { @@ -51,15 +51,6 @@ const PAYPAL_PAYMENT_METHOD = 7; /** * Maps GraphQL validation error field names to form field names. - * - * The Impact API returns UpperCamelCase field names (e.g. `BankAccountNumber`). - * The GraphQL layer in `UserServiceImpl.java` converts these to lowerCamelCase - * via `CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, errorDto.field)`. - * - * Most converted field names already match the form field names exactly - * (e.g. `bankAccountNumber`, `swiftCode`, `routingCode`). Only entries - * where the GraphQL field name differs from the form field name need - * to be listed here. */ const API_FIELD_TO_FORM_FIELD: Record = { // bankProvinceState → form uses bankState @@ -69,11 +60,6 @@ const API_FIELD_TO_FORM_FIELD: Record = { /** * Maps Impact API error code paths (from validationErrors[].errorPath) to short, * readable frontend error codes used in the ICU select props. - * - * The Impact API returns a stable dot-delimited error path (e.g. - * "withdrawal.settings.error.routingcode") alongside the human-readable message. - * The GraphQL layer exposes this as `errorPath`. The keys below match those paths - * to short frontend codes used in the ICU `{errorCode, select, ...}` props. */ const API_ERROR_PATH_TO_FRONTEND: Record = { // Beneficiary account name @@ -309,7 +295,7 @@ function parseImpactThreshold(threshold: string) { } export function useBankingInfoForm( - props: BankingInfoForm + props: BankingInfoForm, ): BankingInfoFormViewProps { const host = useHost(); const locale = useLocale(); @@ -326,7 +312,7 @@ export function useBankingInfoForm( loading: paymentOptionsLoading, errors: paymentOptionsError, } = useParentQueryValue( - FINANCE_NETWORK_SETTINGS_NAMESPACE + FINANCE_NETWORK_SETTINGS_NAMESPACE, ); const { data: userData, @@ -335,11 +321,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); @@ -377,14 +363,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 @@ -415,7 +401,7 @@ export function useBankingInfoForm( paypalEmailAddress: withdrawalSettings.paypalEmailAddress, paymentSchedulingType: withdrawalSettings.paymentSchedulingType, paymentThreshold: parseImpactThreshold( - withdrawalSettings.paymentThreshold + withdrawalSettings.paymentThreshold, ), paymentDay: withdrawalSettings.paymentDay, }; @@ -438,7 +424,7 @@ export function useBankingInfoForm( setPaymentMethodChecked( initialData.paymentMethod === "PAYPAL" ? "toPayPalAccount" - : "toBankAccount" + : "toBankAccount", ); setCurrentPaymentOption(currentPaymentOption); setPaymentScheduleChecked(initialData.paymentSchedulingType); @@ -452,8 +438,8 @@ export function useBankingInfoForm( } else { setFilteredCountries( countries.filter((c) => - c.displayName.toLowerCase().includes(countrySearch.toLowerCase()) - ) || [] + c.displayName.toLowerCase().includes(countrySearch.toLowerCase()), + ) || [], ); } }, [countrySearch, countries]); @@ -535,7 +521,7 @@ export function useBankingInfoForm( }, }; }, - {} + {}, ); setErrors({ @@ -606,12 +592,12 @@ export function useBankingInfoForm( new CustomEvent(VERIFICATION_EVENT_KEY, { detail: { token }, bubbles: false, - }) + }), ); }; function setPaymentMethodChecked( - paymentMethod: "toBankAccount" | "toPayPalAccount" + paymentMethod: "toBankAccount" | "toPayPalAccount", ) { _setPaymentMethodChecked(paymentMethod); @@ -623,7 +609,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); } From 0b82626132ef720b59ba9f94a9cafaa92744b55b Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 7 Apr 2026 10:09:34 -0700 Subject: [PATCH 06/64] Create usePartnerInfoModal and modify view/controller --- packages/mint-components/src/components.d.ts | 10 + .../sqm-partner-info-modal/readme.md | 42 +++ .../sqm-partner-info-modal-view.tsx | 2 +- .../sqm-partner-info-modal.tsx | 17 +- .../usePartnerInfoModal.tsx | 251 ++++++++++++++++++ .../src/components/sqm-stencilbook/readme.md | 2 + .../useTaxAndCashDashboard.tsx | 10 +- 7 files changed, 327 insertions(+), 7 deletions(-) create mode 100644 packages/mint-components/src/components/sqm-partner-info-modal/readme.md create mode 100644 packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 2854b6f8e9..48e02edbf6 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -2312,6 +2312,11 @@ export namespace Components { "paginationText": string; } interface SqmPartnerInfoModal { + /** + * Brand name shown in the modal header + * @uiName Brand name + */ + "brandName": string; /** * @uiName Confirm button label */ @@ -10415,6 +10420,11 @@ declare namespace LocalJSX { "paginationText"?: string; } interface SqmPartnerInfoModal { + /** + * Brand name shown in the modal header + * @uiName Brand name + */ + "brandName"?: string; /** * @uiName Confirm button label */ 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..5fd03ab9ab --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -0,0 +1,42 @@ +# sqm-partner-info-modal + + + + + + +## Properties + +| Property | Attribute | Description | Type | Default | +| ---------------------------- | ------------------------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | +| `brandName` | `brand-name` | Brand name shown in the modal header | `string` | `""` | +| `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; brandName: string; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; }; }` | `undefined` | +| `descriptionExistingPartner` | `description-existing-partner` | Description for existing partner confirmation | `string` | `"We noticed you are already an Impact.com partner, please confirm your information."` | +| `descriptionNewPartner` | `description-new-partner` | Description for new partner setup | `string` | `"We just need a bit more information about you before you start earning cash!"` | +| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please select both a country and currency."` | +| `modalBrandHeader` | `modal-brand-header` | Header text when user has no existing partner | `string` | `"Welcome to {brandName} Program!"` | +| `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"` | +| `submitButtonLabel` | `submit-button-label` | | `string` | `"Submit"` | + + +## Dependencies + +### Used by + + - [sqm-stencilbook](../sqm-stencilbook) + +### Graph +```mermaid +graph TD; + sqm-stencilbook --> 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 index 9d2d768964..a5b9afe33a 100644 --- 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 @@ -106,7 +106,7 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { return ( ; } 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..8705088429 --- /dev/null +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -0,0 +1,251 @@ +import { + useLocale, + useMutation, + useQuery, + useUserIdentity, +} from "@saasquatch/component-boilerplate"; +import { 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 { ConnectPartnerResult } from "../tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm"; +import { validTaxDocument } from "../tax-and-cash/utils"; +import { TAX_FORM_UPDATED_EVENT_KEY } from "../tax-and-cash/eventKeys"; + +export const GET_USER_PARTNER_INFO = gql` + query getUserPartnerInfo { + user: viewer { + ... on User { + id + firstName + lastName + email + countryCode + customFields + impactConnection { + connected + publisher { + 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 + } + } + } +`; + +export const CONNECT_PARTNER = gql` + mutation createImpactConnection($vars: ImpactConnectionInput!) { + createImpactConnection(impactConnectionInput: $vars) { + success + validationErrors { + field + message + } + user { + id + accountId + impactConnection { + connected + publisher { + brandedSignup + requiredTaxDocumentType + currentTaxDocument { + type + status + } + } + } + } + } + } +`; + +const GET_BRAND_NAME = gql` + query getTenantSettings { + tenantSettings { + companyName + } + } +`; + +export type TaxCountry = { + countryCode: string; + displayName: string; +}; + +export type CountriesQuery = { + impactPayoutCountries: { + data: TaxCountry[]; + }; +}; + +type TenantSettingsQuery = { + tenantSettings: { + companyName: string; + }; +}; + +export function usePartnerInfoModal( + props: PartnerInfoModal, +): PartnerInfoModalViewProps { + const user = useUserIdentity(); + const locale = useLocale(); + + const { + data: userData, + loading: userLoading, + refetch, + } = useQuery(GET_USER_PARTNER_INFO, {}, !user?.jwt); + + const { data: currenciesData } = useQuery( + GET_CURRENCIES, + { variables: { locale } }, + !user?.jwt, + ); + + const { data: countriesData } = useQuery(GET_COUNTRIES, {}, !user?.jwt); + + const { data: tenantSettingsData } = useQuery( + GET_BRAND_NAME, + {}, + ); + + const [ + connectImpactPartner, + { loading: connectLoading, errors: connectErrors }, + ] = useMutation(CONNECT_PARTNER); + + const [countryCode, setCountryCode] = useState(""); + const [currency, setCurrency] = useState(""); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + + console.log(user, "user identity in create partner modal"); // TEMP + console.log(userData, "user data from partner info query"); // TEMP + + const impactConnection = userData?.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(""); + } + + async function onSubmit() { + if (!countryCode || !currency) { + setError(props.missingFieldsErrorText); + return; + } + setError(""); + + // AL TODO: How to create impact connection if we dont have address, postalCode, and city? + try { + const vars = { + user: { + id: user.id, + accountId: user.accountId, + }, + firstName: userData.user.firstName, + lastName: userData.user.lastName, + countryCode, + currency, + }; + + const result = await connectImpactPartner({ vars }); + + if (!result || (result as Error)?.message) { + setError(props.networkErrorText); + return; + } + + const connectionResult = (result as ConnectPartnerResult) + .createImpactConnection; + + console.log( + result, + connectionResult, + "result and connectionResult from creating partner from modal", + ); + + 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; + } + + // Success + window.dispatchEvent(new Event(TAX_FORM_UPDATED_EVENT_KEY)); + + await refetch(); + setSuccess(true); + } catch (e) { + console.error("Partner creation error:", e); + setError(props.networkErrorText); + } + } + + const shouldShow = !success && !userLoading && !impactConnection?.connected; + + return { + states: { + open: shouldShow, + loading: userLoading, + submitting: connectLoading, + isExistingPartner: !!impactConnection?.connected, + countryCode, + currency, + error, + success, + brandName: tenantSettingsData?.tenantSettings?.companyName || "", + filteredCountries: countriesData?.impactPayoutCountries?.data || [], + filteredCurrencies: currenciesData?.currencies?.data || [], + }, + callbacks: { + onCountryChange, + onCurrencyChange, + onCountrySearch: () => {}, + onCurrencySearch: () => {}, + onSubmit, + onClose: () => setSuccess(true), + }, + text: props.getTextProps(), + }; +} diff --git a/packages/mint-components/src/components/sqm-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index ea883e9018..d893966744 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -104,6 +104,7 @@ - [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) ### Graph ```mermaid @@ -205,6 +206,7 @@ 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-form-message --> sqm-skeleton sqm-portal-register --> sqm-form-message sqm-portal-register --> sqm-password-field 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, From f08069cfae6a49e499cce81c1d3f91626203ad0e Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Tue, 7 Apr 2026 13:53:48 -0700 Subject: [PATCH 07/64] update query to match backend schema --- packages/mint-components/package.json | 2 +- .../tax-and-cash/sqm-banking-info-form/useBankingInfoForm.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index ac9c3990c4..be92985855 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.1.8-0", + "version": "2.1.8-1", "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", 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 497fb78815..f8303c24ad 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 @@ -252,7 +252,7 @@ const SAVE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message - errorPath + code } } } @@ -269,7 +269,7 @@ const UPDATE_WITHDRAWAL_SETTINGS = gql` validationErrors { field message - errorPath + code } } } From df465433775e307d9b5226860626ef1ab0c8d0e0 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 8 Apr 2026 17:28:28 -0700 Subject: [PATCH 08/64] Update usePartnerInfoModal. Update views for partner modal. Update useUserInfoForm and useTaxAndCash step logic. Handle Impact API sending back DZ and 000000 for phone number data --- packages/mint-components/src/components.d.ts | 28 ++++- .../PartnerInfoModal.stories.tsx | 11 +- .../sqm-partner-info-modal-view.tsx | 69 ++++++---- .../sqm-partner-info-modal.tsx | 54 ++++---- .../usePartnerInfoModal.tsx | 119 ++++++++++++++---- .../sqm-tax-and-cash/useTaxAndCash.tsx | 31 +++-- .../sqm-user-info-form-view.tsx | 43 ++++--- .../sqm-user-info-form/useUserInfoForm.tsx | 10 +- 8 files changed, 258 insertions(+), 107 deletions(-) diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 48e02edbf6..b90fef99c1 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -2356,7 +2356,13 @@ export namespace Components { * @uiName New partner header * @uiWidget textArea */ - "modalBrandHeader": string; + "modalHeader": string; + /** + * Header text when user has an existing partner + * @uiName Existing partner header + * @uiWidget textArea + */ + "modalHeaderExistingPartner": string; /** * @uiName Network error text * @uiWidget textArea @@ -2374,6 +2380,12 @@ export namespace Components { * @uiName Submit button label */ "submitButtonLabel": string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + * @uiWidget textArea + */ + "supportDescriptionExistingPartner": string; } interface SqmPasswordField { /** @@ -10464,7 +10476,13 @@ declare namespace LocalJSX { * @uiName New partner header * @uiWidget textArea */ - "modalBrandHeader"?: string; + "modalHeader"?: string; + /** + * Header text when user has an existing partner + * @uiName Existing partner header + * @uiWidget textArea + */ + "modalHeaderExistingPartner"?: string; /** * @uiName Network error text * @uiWidget textArea @@ -10482,6 +10500,12 @@ declare namespace LocalJSX { * @uiName Submit button label */ "submitButtonLabel"?: string; + /** + * Support description for existing partner confirmation + * @uiName Existing partner support description + * @uiWidget textArea + */ + "supportDescriptionExistingPartner"?: string; } interface SqmPasswordField { /** 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 index ca87628da2..214f077d2c 100644 --- 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 @@ -29,18 +29,21 @@ const demoCurrencies = [ const noopCallbacks = { onCountryChange: (e: any) => console.log("Country changed:", e), onCurrencyChange: (e: any) => console.log("Currency changed:", e), - onCountrySearch: (v: string) => console.log("Country search:", v), - onCurrencySearch: (v: string) => console.log("Currency search:", v), + 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 = { - modalBrandHeader: "Welcome to {brandName} Program!", + modalHeader: "Let's get you ready for rewards", + modalHeaderExistingPartner: "We found an existing account", descriptionNewPartner: - "We just need a bit more information about you before you start earning cash!", + "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.", countryLabel: "Country", currencyLabel: "Currency", submitButtonLabel: "Submit", 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 index a5b9afe33a..fa1e4f5a59 100644 --- 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 @@ -19,13 +19,13 @@ export interface PartnerInfoModalViewProps { callbacks: { onCountryChange: (e: any) => void; onCurrencyChange: (e: any) => void; - onCountrySearch: (value: string) => void; - onCurrencySearch: (value: string) => void; + setCurrencySearch: (c: any) => void; + setCountrySearch: (c: any) => void; onSubmit: () => void; onClose: () => void; }; text: { - modalBrandHeader: string; + modalHeader: string; descriptionNewPartner: string; descriptionExistingPartner: string; countryLabel: string; @@ -34,6 +34,8 @@ export interface PartnerInfoModalViewProps { confirmButtonLabel: string; searchCountryPlaceholder: string; searchCurrencyPlaceholder: string; + supportDescriptionExistingPartner: string; + modalHeaderExistingPartner: string; }; } @@ -69,6 +71,7 @@ const style = { }, FormFields: { display: "flex", + flexDirection: "column", gap: "var(--sl-spacing-medium)", marginTop: "var(--sl-spacing-medium)", @@ -88,6 +91,15 @@ const style = { borderRadius: "0", }, }, + DescriptionContainer: { + display: "flex", + flexDirection: "column", + gap: "var(--sl-spacing-medium)", + marginTop: "var(--sl-spacing-small)", + "& > p": { + margin: "0", + }, + }, }; export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { @@ -95,9 +107,18 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { const sheet = createStyleSheet(style); const styleString = sheet.toString(); - const description = states.isExistingPartner - ? text.descriptionExistingPartner - : text.descriptionNewPartner; + console.log(states, "partner info modal states"); // TEMP + + if (!states.open) return
; + + const description = states.isExistingPartner ? ( +
+

{text.descriptionExistingPartner}

+

{text.supportDescriptionExistingPartner}

+
+ ) : ( +

{text.descriptionNewPartner}

+ ); const buttonLabel = states.isExistingPartner ? text.confirmButtonLabel @@ -108,13 +129,11 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { class={sheet.classes.Dialog} open={states.open} noHeader={false} - label={intl.formatMessage( - { - id: "modalBrandHeader", - defaultMessage: text.modalBrandHeader, - }, - { brandName: states.brandName }, - )} + label={ + states.isExistingPartner + ? text.modalHeaderExistingPartner + : text.modalHeader + } onSl-request-close={(e: any) => { e.preventDefault(); }} @@ -126,15 +145,14 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { }} > - -

{description}

- + {description}
@@ -142,9 +160,9 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { class={sheet.classes.SearchInput} placeholder={text.searchCountryPlaceholder} onKeyDown={(e: any) => e.stopPropagation()} - onSl-input={(e: any) => - callbacks.onCountrySearch(e.target?.value || "") - } + onSl-input={(e: any) => { + callbacks.setCountrySearch(e.target?.value); + }} /> {states.filteredCountries?.map((c) => ( {c.displayName} @@ -152,10 +170,11 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { @@ -164,19 +183,15 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { placeholder={text.searchCurrencyPlaceholder} onKeyDown={(e: any) => e.stopPropagation()} onSl-input={(e: any) => - callbacks.onCurrencySearch(e.target?.value || "") + callbacks.setCurrencySearch(e.target?.value) } /> {states.filteredCurrencies?.map((c) => ( - - {c.currencyCode} - {c.displayName} - + {c.currencyCode} ))}
- {states.error &&

{states.error}

} - { @@ -183,8 +186,8 @@ function useDemoPartnerInfoModal( const value = e?.detail?.item?.__value; if (value) setCurrency(value); }, - onCountrySearch: () => {}, - onCurrencySearch: () => {}, + setCountrySearch: () => {}, + setCurrencySearch: () => {}, onSubmit: () => { if (!countryCode || !currency) { setError(props.missingFieldsErrorText); @@ -195,7 +198,7 @@ function useDemoPartnerInfoModal( onClose: () => {}, }, text: { - modalBrandHeader: props.modalBrandHeader, + modalHeader: props.modalHeader, descriptionNewPartner: props.descriptionNewPartner, descriptionExistingPartner: props.descriptionExistingPartner, countryLabel: props.countryLabel, @@ -204,6 +207,9 @@ function useDemoPartnerInfoModal( confirmButtonLabel: props.confirmButtonLabel, searchCountryPlaceholder: props.searchCountryPlaceholder, searchCurrencyPlaceholder: props.searchCurrencyPlaceholder, + supportDescriptionExistingPartner: + props.supportDescriptionExistingPartner, + modalHeaderExistingPartner: props.modalHeaderExistingPartner, }, }, props.demoData || {}, 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 index 8705088429..9c3172659d 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -4,7 +4,7 @@ import { useQuery, useUserIdentity, } from "@saasquatch/component-boilerplate"; -import { useState } from "@saasquatch/universal-hooks"; +import { useEffect, 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"; @@ -12,11 +12,13 @@ import { ConnectPartnerResult } from "../tax-and-cash/sqm-indirect-tax-form/useI import { validTaxDocument } from "../tax-and-cash/utils"; import { TAX_FORM_UPDATED_EVENT_KEY } from "../tax-and-cash/eventKeys"; +// new field under impactConnection:{ resolvedByEmail: boolean } - determines if connection came from managed identity export const GET_USER_PARTNER_INFO = gql` query getUserPartnerInfo { user: viewer { ... on User { id + accountId firstName lastName email @@ -25,8 +27,18 @@ export const GET_USER_PARTNER_INFO = gql` impactConnection { connected publisher { + phoneNumber + phoneNumberCountryCode countryCode currency + requiredTaxDocumentType + currentTaxDocument { + type + status + } + withdrawalSettings { + paymentMethod + } } } } @@ -111,22 +123,25 @@ type TenantSettingsQuery = { export function usePartnerInfoModal( props: PartnerInfoModal, ): PartnerInfoModalViewProps { - const user = useUserIdentity(); const locale = useLocale(); const { data: userData, loading: userLoading, refetch, - } = useQuery(GET_USER_PARTNER_INFO, {}, !user?.jwt); + } = useQuery(GET_USER_PARTNER_INFO, {}); - const { data: currenciesData } = useQuery( + const user = userData?.user; + + const { data: currenciesData, loading: currenciesLoading } = useQuery( GET_CURRENCIES, { variables: { locale } }, - !user?.jwt, ); - const { data: countriesData } = useQuery(GET_COUNTRIES, {}, !user?.jwt); + const { data: countriesData, loading: countriesLoading } = useQuery( + GET_COUNTRIES, + {}, + ); const { data: tenantSettingsData } = useQuery( GET_BRAND_NAME, @@ -138,15 +153,65 @@ export function usePartnerInfoModal( { loading: connectLoading, errors: connectErrors }, ] = useMutation(CONNECT_PARTNER); - const [countryCode, setCountryCode] = useState(""); - const [currency, setCurrency] = useState(""); + const [countryCode, setCountryCode] = useState( + user?.impactConnection?.publisher?.countryCode || "", + ); + const [currency, setCurrency] = useState( + user?.impactConnection?.publisher?.currency || "", + ); + + console.log(countryCode, currency, "initial country and currency state"); // TEMP + const [countrySearch, setCountrySearch] = useState(""); + const [currencySearch, setCurrencySearch] = useState(""); + const [filteredCountries, setFilteredCountries] = useState( + countriesData?.impactPayoutCountries?.data || [], + ); + const [filteredCurrencies, setFilteredCurrencies] = useState( + currenciesData?.currencies?.data || [], + ); + const [error, setError] = useState(""); const [success, setSuccess] = useState(false); - console.log(user, "user identity in create partner modal"); // TEMP - console.log(userData, "user data from partner info query"); // TEMP + const countries = countriesData?.impactPayoutCountries?.data || []; + const currencies = currenciesData?.currencies?.data || []; - const impactConnection = userData?.user?.impactConnection; + console.log(user, "user data from partner info query"); // TEMP + + useEffect(() => { + if (userData && user.impactConnection?.publisher) { + setCountryCode(user.impactConnection.publisher.countryCode); + setCurrency(user.impactConnection.publisher.currency); + } + }, [userData]); + + 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; @@ -170,15 +235,16 @@ export function usePartnerInfoModal( } setError(""); - // AL TODO: How to create impact connection if we dont have address, postalCode, and city? try { const vars = { user: { id: user.id, accountId: user.accountId, }, - firstName: userData.user.firstName, - lastName: userData.user.lastName, + firstName: user.firstName, + lastName: user.lastName, + // phoneNumber: null, + // phoneNumberCountryCode: null, countryCode, currency, }; @@ -211,7 +277,6 @@ export function usePartnerInfoModal( return; } - // Success window.dispatchEvent(new Event(TAX_FORM_UPDATED_EVENT_KEY)); await refetch(); @@ -222,27 +287,35 @@ export function usePartnerInfoModal( } } - const shouldShow = !success && !userLoading && !impactConnection?.connected; + console.log(success, "success state in partner info modal"); + + const showModal = + !success && + !userLoading && + (!impactConnection?.connected || !impactConnection?.publisher); + + console.log(showModal, "showModal condition in partner info modal"); // TEMP return { states: { - open: shouldShow, - loading: userLoading, + open: showModal, + loading: userLoading || countriesLoading || currenciesLoading, submitting: connectLoading, - isExistingPartner: !!impactConnection?.connected, + isExistingPartner: + impactConnection?.connected || impactConnection?.publisher, countryCode, currency, error, success, brandName: tenantSettingsData?.tenantSettings?.companyName || "", - filteredCountries: countriesData?.impactPayoutCountries?.data || [], - filteredCurrencies: currenciesData?.currencies?.data || [], + filteredCountries: filteredCountries || [], + filteredCurrencies: filteredCurrencies || [], }, callbacks: { onCountryChange, onCurrencyChange, - onCountrySearch: () => {}, - onCurrencySearch: () => {}, + setCurrencySearch, + setCountrySearch, onSubmit, onClose: () => setSuccess(true), }, 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..2a7df7ae10 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 @@ -44,7 +44,24 @@ function getCurrentStep(user: UserQuery["user"]) { withdrawalSettings, brandedSignup, payoutsAccount, + billingAddress, + phoneNumber, + billingCity, + billingPostalCode, + billingState, } = user.impactConnection.publisher; + console.log(phoneNumber, "phone number in getCurrentStep"); + + // Early partner creation does not collect these fields + if ( + !billingAddress || + !billingCity || + !billingPostalCode || + !billingState || + !phoneNumber + ) { + return "/1"; + } // If they do have a required document, look at current document if (requiredTaxDocumentType && !currentTaxDocument) { @@ -97,7 +114,7 @@ export function useTaxAndCash() { { namespace: CURRENCIES_NAMESPACE, initialValue: [], - } + }, ); const [_countriesContext, _setCountriesContext] = useParentState< @@ -158,7 +175,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 +193,7 @@ export function useTaxAndCash() { return [...agg, currency]; }, - [] + [], ); return allValidCurrencies; }, [financeNetworkData, countryCode]); @@ -194,9 +211,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 +222,7 @@ export function useTaxAndCash() { () => Array.from(availableCountries) .map((countryCode) => - getCountryObj({ countryCode, locale: intlLocale }) + getCountryObj({ countryCode, locale: intlLocale }), ) .sort(sortByName) .reduce((prev, countryObj) => { @@ -213,7 +230,7 @@ export function useTaxAndCash() { return [countryObj, ...prev]; return [...prev, countryObj]; }, []), - [availableCountries] + [availableCountries], ); useEffect(() => { 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..fb95a0cbe2 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 @@ -413,7 +413,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.termsAndConditionsLabel} ), - } + }, ); let regionLabel = undefined; @@ -431,7 +431,14 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { regionLabel = text.state; } + // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "000000" and the phoneNumberCountryCode to "DZ" function isDisabledPartnerInput(field: string) { + if ( + data.partnerData.phoneNumber === "0000000" && + (field === "phoneNumber" || field === "phoneNumberCountryCode") + ) { + return false; + } return states.isPartner && !!data.partnerData?.[field]; } @@ -469,7 +476,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.supportLink} ), - } + }, )}

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

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

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

@@ -561,7 +568,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.firstName, - formState.errors.firstName + formState.errors.firstName, ), } : {})} @@ -579,7 +586,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.lastName, - formState.errors.lastName + formState.errors.lastName, ), } : {})} @@ -611,7 +618,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.country, - formState.errors.countryCode + formState.errors.countryCode, ), } : {})} @@ -710,7 +717,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { validateBillingField(/[a-zA-Z]+/, value) && formatErrorMessage( text.phoneNumber, - text.error.fieldInvalidError + text.error.fieldInvalidError, ); }} disabled={ @@ -721,7 +728,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.phoneNumber, - formState.errors.phoneNumber + formState.errors.phoneNumber, ), } : {})} @@ -740,7 +747,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.address, - text.error.invalidCharacterError + text.error.invalidCharacterError, ) } disabled={ @@ -751,7 +758,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.address, - formState.errors.address + formState.errors.address, ), } : {})} @@ -768,7 +775,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.city, - text.error.invalidCharacterError + text.error.invalidCharacterError, ) } disabled={ @@ -779,7 +786,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.city, - formState.errors.city + formState.errors.city, ), } : {})} @@ -800,7 +807,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.state, - formState.errors.state + formState.errors.state, ), } : {})} @@ -825,7 +832,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.postalCode, - formState.errors.postalCode + formState.errors.postalCode, ), } : {})} @@ -845,7 +852,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.currency, - formState.errors.currency + formState.errors.currency, ), } : {})} @@ -896,7 +903,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => {

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

)} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index 35f398a025..cac86cdb83 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -149,9 +149,15 @@ export function useUserInfoForm(props: TaxForm) { lastName: user.impactConnection.user.lastName, countryCode: user.impactConnection.publisher.countryCode, currency: user.impactConnection.publisher.currency, + // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "000000" and the phoneNumberCountryCode to "DZ" phoneNumberCountryCode: - user.impactConnection.publisher.phoneNumberCountryCode, - phoneNumber: user.impactConnection.publisher.phoneNumber, + user.impactConnection.publisher.phoneNumber === "0000000" + ? null + : user.impactConnection.publisher.phoneNumberCountryCode, + phoneNumber: + user.impactConnection.publisher.phoneNumber === "0000000" + ? null + : user.impactConnection.publisher.phoneNumber, address: user.impactConnection.publisher.billingAddress, city: user.impactConnection.publisher.billingCity, state: user.impactConnection.publisher.billingState, From f1698469a76780e3509c10afc6bd4f74ae979b7d Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 9 Apr 2026 16:33:10 -0700 Subject: [PATCH 09/64] Add sqm-partner-info-modal to sqm-widget-verification --- packages/mint-components/package.json | 2 +- .../sqm-partner-info-modal/readme.md | 34 +++++++++++-------- .../sqm-widget-verification.tsx | 6 +++- .../useWidgetVerification.ts | 8 ++++- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index be92985855..9b946c3fc8 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.1.8-1", + "version": "2.1.9-1", "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", 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 index 5fd03ab9ab..0385e4bec0 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -7,21 +7,23 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ---------------------------- | ------------------------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------- | -| `brandName` | `brand-name` | Brand name shown in the modal header | `string` | `""` | -| `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; brandName: string; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; }; }` | `undefined` | -| `descriptionExistingPartner` | `description-existing-partner` | Description for existing partner confirmation | `string` | `"We noticed you are already an Impact.com partner, please confirm your information."` | -| `descriptionNewPartner` | `description-new-partner` | Description for new partner setup | `string` | `"We just need a bit more information about you before you start earning cash!"` | -| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please select both a country and currency."` | -| `modalBrandHeader` | `modal-brand-header` | Header text when user has no existing partner | `string` | `"Welcome to {brandName} Program!"` | -| `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"` | -| `submitButtonLabel` | `submit-button-label` | | `string` | `"Submit"` | +| Property | Attribute | Description | Type | Default | +| ----------------------------------- | -------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `brandName` | `brand-name` | Brand name shown in the modal header | `string` | `""` | +| `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; brandName: string; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; }; }` | `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."` | +| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please select both a country and currency."` | +| `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"` | +| `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 Support or sign up for this referral program with a different email."` | ## Dependencies @@ -29,11 +31,13 @@ ### Used by - [sqm-stencilbook](../sqm-stencilbook) + - [sqm-widget-verification](../sqm-widget-verification) ### Graph ```mermaid graph TD; 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 ``` 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..b4d1abfce6 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 @@ -165,6 +165,10 @@ export class WidgetVerification { if (props.loading) return ; + if (props.showPartnerModal) { + return ; + } + const generalText = this.getStepTextProps("general_"); return ( @@ -223,7 +227,7 @@ function useDemoWidgetVerificationInternal(props: WidgetVerification) { }; return deepmerge( - { showCode, onVerification, loading: false }, + { showCode, showPartnerModal: false, onVerification, onPartnerModalComplete: () => {}, loading: false }, formatted || {}, { 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..6c42cf17d8 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -42,6 +42,7 @@ export function useWidgetVerification() { }); const setContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); const [loading, setLoading] = useState(true); + const [showPartnerModal, setShowPartnerModal] = useState(false); const [fetch] = useLazyQuery(USER_LOOKUP); useEffect(() => { @@ -63,8 +64,13 @@ export function useWidgetVerification() { }, []); const onVerification = () => { + setShowPartnerModal(true); + }; + + const onPartnerModalComplete = () => { + setShowPartnerModal(false); setContext(true); }; - return { showCode, onVerification, loading }; + return { showCode, showPartnerModal, onVerification, onPartnerModalComplete, loading }; } From b094ed341b28cc342ac7b6491e96dd8c432793b1 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 10 Apr 2026 16:31:04 -0700 Subject: [PATCH 10/64] Update code/email verification components to live inside modals. Bake partner creation in to widget verificaiton flow. Some view refactors for all 3 components --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 166 ++++++++++++++- .../sqm-partner-info-modal/readme.md | 1 + .../sqm-partner-info-modal-view.tsx | 4 +- .../sqm-partner-info-modal.tsx | 22 +- .../usePartnerInfoModal.tsx | 9 +- .../src/components/sqm-stencilbook/readme.md | 5 + .../sqm-stencilbook/sqm-stencilbook.tsx | 2 + .../WidgetVerification.stories.tsx | 37 ++++ .../sqm-code-verification-view.tsx | 15 +- .../sqm-email-verification-view.tsx | 6 +- .../sqm-widget-verification.tsx | 201 ++++++++++++++++-- .../useWidgetVerification.ts | 18 +- .../sqm-user-info-form-view.tsx | 8 +- 14 files changed, 451 insertions(+), 45 deletions(-) create mode 100644 packages/mint-components/src/components/sqm-widget-verification/WidgetVerification.stories.tsx diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 9b946c3fc8..940405e9b7 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.1.9-1", + "version": "2.1.9-9", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index b90fef99c1..6955a47d47 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -34,7 +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 { PartnerInfoModalViewProps } from "./components/sqm-partner-info-modal/sqm-partner-info-modal-view"; +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"; @@ -2333,7 +2333,7 @@ export namespace Components { * @undocumented * @uiType object */ - "demoData"?: DemoData; + "demoData"?: DemoData; /** * Description for existing partner confirmation * @uiName Existing partner description @@ -2376,6 +2376,13 @@ export namespace Components { * @uiName Search currency placeholder */ "searchCurrencyPlaceholder": string; + /** + * @undocumented + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + * @componentState { "title": "Connected (hidden)", "props": { "states": { "open": false } } } + */ + "stateController": string; /** * @uiName Submit button label */ @@ -7223,6 +7230,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 @@ -7267,6 +7346,7 @@ export namespace Components { * @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; } @@ -10453,7 +10533,7 @@ declare namespace LocalJSX { * @undocumented * @uiType object */ - "demoData"?: DemoData; + "demoData"?: DemoData; /** * Description for existing partner confirmation * @uiName Existing partner description @@ -10496,6 +10576,13 @@ declare namespace LocalJSX { * @uiName Search currency placeholder */ "searchCurrencyPlaceholder"?: string; + /** + * @undocumented + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + * @componentState { "title": "Connected (hidden)", "props": { "states": { "open": false } } } + */ + "stateController"?: string; /** * @uiName Submit button label */ @@ -15318,6 +15405,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 @@ -15362,6 +15521,7 @@ declare namespace LocalJSX { * @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; } 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 index 0385e4bec0..ca7da3d119 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -22,6 +22,7 @@ | `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` | `"{}"` | | `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 Support or sign up for this referral program with a different email."` | 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 index fa1e4f5a59..719431c89f 100644 --- 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 @@ -51,7 +51,7 @@ const style = { "var(--sl-spacing-x-large) var(--sl-spacing-x-large) 0 var(--sl-spacing-x-large)", }, "&::part(body)": { - padding: "var(--sl-spacing-small) var(--sl-spacing-x-large)", + padding: "0 var(--sl-spacing-x-large)", fontSize: "var(--sl-font-size-small)", overflow: "visible", }, @@ -59,8 +59,6 @@ const style = { display: "flex", flexDirection: "column", gap: "var(--sl-spacing-small)", - padding: - "var(--sl-spacing-small) var(--sl-spacing-x-large) var(--sl-spacing-x-large)", }, "&::part(overlay)": { background: "rgba(0, 0, 0, 0.5)", 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 index 44e1b2eebc..cc49a4d928 100644 --- 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 @@ -3,12 +3,16 @@ 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 { PartnerInfoModalView, PartnerInfoModalViewProps, } from "./sqm-partner-info-modal-view"; -import { usePartnerInfoModal } from "./usePartnerInfoModal"; +import { + PartnerInfoModalResult, + usePartnerInfoModal, +} from "./usePartnerInfoModal"; /** * @uiName Partner Info Modal @@ -127,11 +131,19 @@ export class PartnerInfoModal { @Prop() missingFieldsErrorText: string = "Please select both a country and currency."; + /** + * @undocumented + * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } + * @componentState { "title": "Existing partner", "props": { "states": { "open": true, "isExistingPartner": true, "countryCode": "US", "currency": "USD" } } } + * @componentState { "title": "Connected (hidden)", "props": { "states": { "open": false } } } + */ + @Prop() stateController: string = "{}"; + /** * @undocumented * @uiType object */ - @Prop() demoData?: DemoData; + @Prop() demoData?: DemoData; constructor() { withHooks(this); @@ -159,6 +171,10 @@ function useDemoPartnerInfoModal( const [currency, setCurrency] = useState(""); const [error, setError] = useState(""); + const parsed = parseStates(props.stateController); + const stateOverride = parsed?.["sqm-partner-info-modal"] || parsed || {}; + + // @ts-ignore return deepmerge( { states: { @@ -212,7 +228,7 @@ function useDemoPartnerInfoModal( modalHeaderExistingPartner: props.modalHeaderExistingPartner, }, }, - props.demoData || {}, + 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 index 9c3172659d..61c2054061 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -2,6 +2,7 @@ import { useLocale, useMutation, useQuery, + useSetParent, useUserIdentity, } from "@saasquatch/component-boilerplate"; import { useEffect, useState } from "@saasquatch/universal-hooks"; @@ -11,6 +12,7 @@ import { PartnerInfoModalViewProps } from "./sqm-partner-info-modal-view"; import { ConnectPartnerResult } from "../tax-and-cash/sqm-indirect-tax-form/useIndirectTaxForm"; import { validTaxDocument } from "../tax-and-cash/utils"; import { TAX_FORM_UPDATED_EVENT_KEY } from "../tax-and-cash/eventKeys"; +import { VERIFICATION_PARENT_NAMESPACE } from "../sqm-widget-verification/keys"; // new field under impactConnection:{ resolvedByEmail: boolean } - determines if connection came from managed identity export const GET_USER_PARTNER_INFO = gql` @@ -124,6 +126,7 @@ export function usePartnerInfoModal( props: PartnerInfoModal, ): PartnerInfoModalViewProps { const locale = useLocale(); + const setVerificationContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); const { data: userData, @@ -281,6 +284,7 @@ export function usePartnerInfoModal( await refetch(); setSuccess(true); + setVerificationContext(true); } catch (e) { console.error("Partner creation error:", e); setError(props.networkErrorText); @@ -301,8 +305,7 @@ export function usePartnerInfoModal( open: showModal, loading: userLoading || countriesLoading || currenciesLoading, submitting: connectLoading, - isExistingPartner: - impactConnection?.connected || impactConnection?.publisher, + isExistingPartner: impactConnection?.publisher, countryCode, currency, error, @@ -322,3 +325,5 @@ export function usePartnerInfoModal( text: props.getTextProps(), }; } + +export type PartnerInfoModalResult = ReturnType; diff --git a/packages/mint-components/src/components/sqm-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index d893966744..3b35b01050 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -105,6 +105,7 @@ - [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) ### Graph ```mermaid @@ -207,6 +208,7 @@ graph TD; sqm-stencilbook --> sqm-lead-dropdown-field sqm-stencilbook --> sqm-lead-form sqm-stencilbook --> sqm-partner-info-modal + sqm-stencilbook --> sqm-widget-verification sqm-form-message --> sqm-skeleton sqm-portal-register --> sqm-form-message sqm-portal-register --> sqm-password-field @@ -285,6 +287,9 @@ graph TD; sqm-payout-status-alert --> sqm-form-message sqm-lead-form --> sqm-form-message sqm-lead-form --> sqm-lead-input-field + sqm-widget-verification --> sqm-partner-info-modal + sqm-widget-verification --> sqm-code-verification + sqm-widget-verification --> sqm-email-verification 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 ffe841f413..f694d18bd8 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx +++ b/packages/mint-components/src/components/sqm-stencilbook/sqm-stencilbook.tsx @@ -109,6 +109,7 @@ 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 { ShadowViewAddon } from "../../ShadowViewAddon"; import { CucumberAddon } from "./CucumberAddon"; @@ -223,6 +224,7 @@ const stories = [ TaxAndCashReferralTableRewardsCell, TaxAndCashReferralTable, PartnerInfoModal, + WidgetVerification, ]; /** 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/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-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-widget-verification.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx index b4d1abfce6..76fcdd15e3 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,7 @@ import { VERIFICATION_PARENT_NAMESPACE, } from "./keys"; import { useWidgetVerification } from "./useWidgetVerification"; +import { createStyleSheet } from "../../styling/JSS"; /** * @uiName Widget Verification Flow @@ -26,7 +27,7 @@ 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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ @@ -87,7 +88,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 +142,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 = "{}"; @@ -166,13 +263,51 @@ export class WidgetVerification { if (props.loading) return ; if (props.showPartnerModal) { - return ; + return ( + + ); } + const style = { + Dialog: { + "&::part(panel)": { + maxWidth: "480px", + }, + "&::part(title)": { + fontSize: "var(--sl-font-size-x-large)", + fontWeight: "600", + padding: + "var(--sl-spacing-x-large) var(--sl-spacing-x-large) 0 var(--sl-spacing-x-large)", + }, + "&::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)", + }, + "&::part(close-button)": { + display: "none", + }, + }, + }; + + const sheet = createStyleSheet(style); + const styleString = sheet.toString(); + const generalText = this.getStepTextProps("general_"); return (
+

{generalText.verifyEmailHeader}

@@ -186,16 +321,46 @@ export class WidgetVerification { {generalText.verifyEmailDescription}

{props.showCode ? ( - + { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + > + + ) : ( - + { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + > + + )}
); @@ -219,7 +384,7 @@ function useDemoWidgetVerificationInternal(props: WidgetVerification) { key === "sqm-widget-verification" ? { ...prev, ...states[key] } : { ...prev, [`${key}_stateController`]: states[key] }, - {} + {}, ); const onVerification = () => { @@ -227,8 +392,14 @@ function useDemoWidgetVerificationInternal(props: WidgetVerification) { }; return deepmerge( - { showCode, showPartnerModal: false, onVerification, onPartnerModalComplete: () => {}, 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 6c42cf17d8..0c58961cb3 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -25,6 +25,9 @@ const USER_LOOKUP = gql` email emailVerified } + impactConnection { + connected + } } } } @@ -51,8 +54,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 { 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 fb95a0cbe2..dc3771b783 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 @@ -434,7 +434,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "000000" and the phoneNumberCountryCode to "DZ" function isDisabledPartnerInput(field: string) { if ( - data.partnerData.phoneNumber === "0000000" && + data.partnerData?.phoneNumber === "0000000" && (field === "phoneNumber" || field === "phoneNumberCountryCode") ) { return false; @@ -530,8 +530,8 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => {

)} - - {(states.isPartner || states.isUser) && ( + {/* AL: Don't need to show this anymore due to early partner creation */} + {/* {(states.isPartner || states.isUser) && (

{text.isPartnerAlertHeader}

@@ -553,7 +553,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { )}

- )} + )} */}
From 437b7256c9c7eae02e0f80267131faf8e5df9290 Mon Sep 17 00:00:00 2001 From: Andy Date: Mon, 13 Apr 2026 17:10:28 -0700 Subject: [PATCH 11/64] Fix styling issues for inModal partner creation --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 20 ++ .../sqm-partner-info-modal/readme.md | 1 + .../sqm-partner-info-modal-view.tsx | 177 ++++++++++-------- .../sqm-partner-info-modal.tsx | 18 +- .../usePartnerInfoModal.tsx | 1 + .../sqm-widget-verification.tsx | 130 ++++++------- 7 files changed, 192 insertions(+), 157 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 940405e9b7..d657cbdac0 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.1.9-9", + "version": "2.1.9-13", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 6955a47d47..cd148b467c 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -2346,6 +2346,11 @@ export namespace Components { * @uiWidget textArea */ "descriptionNewPartner": string; + /** + * Used to render in another modal. + * @undocumented + */ + "inModal": boolean; /** * @uiName Missing fields error text * @uiWidget textArea @@ -7342,6 +7347,11 @@ export namespace Components { * @uiGroup General Text */ "general_verifyEmailHeader": string; + /** + * @uiName General widget header text with partner creation + * @uiGroup General Text + */ + "general_widgetHeaderWithPartnerCreation": string; /** * @undocumented * @componentState { "title": "Step 1: Enter email", "props": { "showCode": false }, "dependencies": ["sqm-email-verification"], "uiGroup": "Email Verification Step" } @@ -10546,6 +10556,11 @@ declare namespace LocalJSX { * @uiWidget textArea */ "descriptionNewPartner"?: string; + /** + * Used to render in another modal. + * @undocumented + */ + "inModal"?: boolean; /** * @uiName Missing fields error text * @uiWidget textArea @@ -15517,6 +15532,11 @@ declare namespace LocalJSX { * @uiGroup General Text */ "general_verifyEmailHeader"?: string; + /** + * @uiName General widget header text with partner creation + * @uiGroup General Text + */ + "general_widgetHeaderWithPartnerCreation"?: string; /** * @undocumented * @componentState { "title": "Step 1: Enter email", "props": { "showCode": false }, "dependencies": ["sqm-email-verification"], "uiGroup": "Email Verification Step" } 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 index ca7da3d119..5cd1991c94 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -16,6 +16,7 @@ | `demoData` | -- | | `{ states?: { open: boolean; loading: boolean; submitting: boolean; isExistingPartner: boolean; countryCode: string; currency: string; error: string; success: boolean; brandName: string; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; }; }` | `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 select both a country and currency."` | | `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"` | 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 index 719431c89f..9c992e14eb 100644 --- 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 @@ -44,12 +44,6 @@ const style = { "&::part(panel)": { maxWidth: "480px", }, - "&::part(title)": { - fontSize: "var(--sl-font-size-x-large)", - fontWeight: "600", - padding: - "var(--sl-spacing-x-large) var(--sl-spacing-x-large) 0 var(--sl-spacing-x-large)", - }, "&::part(body)": { padding: "0 var(--sl-spacing-x-large)", fontSize: "var(--sl-font-size-small)", @@ -58,14 +52,18 @@ const style = { "&::part(footer)": { display: "flex", flexDirection: "column", - gap: "var(--sl-spacing-small)", + padding: + "0 var(--sl-spacing-large) var(--sl-spacing-large) var(--sl-spacing-large)", }, "&::part(overlay)": { background: "rgba(0, 0, 0, 0.5)", }, - "&::part(close-button)": { - display: "none", - }, + }, + DialogTitle: { + fontSize: "var(--sl-font-size-x-large)", + fontWeight: "600", + padding: "var(--sl-spacing-x-large) 0 0 0", + margin: "0", }, FormFields: { display: "flex", @@ -93,40 +91,113 @@ const style = { display: "flex", flexDirection: "column", gap: "var(--sl-spacing-medium)", - marginTop: "var(--sl-spacing-small)", + margin: "0", "& > p": { margin: "0", }, }, + SubmitButton: { + width: "100%", + marginTop: "var(--sl-spacing-small)", + }, }; -export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { +export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { const { states, callbacks, text } = props; const sheet = createStyleSheet(style); const styleString = sheet.toString(); - console.log(states, "partner info modal states"); // TEMP - - if (!states.open) return
; - const description = states.isExistingPartner ? ( -
+

{text.descriptionExistingPartner}

{text.supportDescriptionExistingPartner}

-
+ ) : ( -

{text.descriptionNewPartner}

+

+ {text.descriptionNewPartner} +

); const buttonLabel = states.isExistingPartner ? text.confirmButtonLabel : text.submitButtonLabel; + 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} + ))} + +
, + states.error &&

{states.error}

, + + {buttonLabel} + , + ]; +} + +export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { + const { states, text } = props; + const sheet = createStyleSheet(style); + + console.log(states, "partner info modal states"); // TEMP + + if (!states.open) return
; + return ( { - // Prevent closing when clicking outside the dialog but not dropdowns if (e.target?.tagName === "SL-DIALOG") { e.preventDefault(); } }} > - - {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} - ))} - -
- {states.error &&

{states.error}

} - - {buttonLabel} - +

+ {states.isExistingPartner + ? text.modalHeaderExistingPartner + : text.modalHeader} +

+
); } 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 index cc49a4d928..49f9a78655 100644 --- 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 @@ -3,9 +3,9 @@ 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"; @@ -131,6 +131,12 @@ export class PartnerInfoModal { @Prop() missingFieldsErrorText: string = "Please select both a country and currency."; + /** + * Used to render in another modal. + * @undocumented + */ + @Prop() inModal: boolean = false; + /** * @undocumented * @componentState { "title": "New partner", "props": { "states": { "open": true, "isExistingPartner": false } } } @@ -155,11 +161,14 @@ export class PartnerInfoModal { } render() { - // AL: TODO add usePartnerInfoModal const props = isDemo() ? useDemoPartnerInfoModal(this) : usePartnerInfoModal(this); + if (this.inModal) { + return ; + } + return ; } } @@ -171,9 +180,6 @@ function useDemoPartnerInfoModal( const [currency, setCurrency] = useState(""); const [error, setError] = useState(""); - const parsed = parseStates(props.stateController); - const stateOverride = parsed?.["sqm-partner-info-modal"] || parsed || {}; - // @ts-ignore return deepmerge( { @@ -228,7 +234,7 @@ function useDemoPartnerInfoModal( modalHeaderExistingPartner: props.modalHeaderExistingPartner, }, }, - props.demoData || stateOverride, + props.demoData || props.stateController || {}, { 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 index 61c2054061..b62cbc78eb 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -29,6 +29,7 @@ export const GET_USER_PARTNER_INFO = gql` impactConnection { connected publisher { + id phoneNumber phoneNumberCountryCode countryCode 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 76fcdd15e3..48152c676d 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 @@ -31,6 +31,12 @@ export class WidgetVerification { /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ GENERAL PROPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ + /** + * @uiName General widget header text with partner creation + * @uiGroup General Text + */ + @Prop() + general_widgetHeaderWithPartnerCreation = "Let's get you ready for rewards"; /** * @uiName General verify widget header text * @uiGroup General Text @@ -262,25 +268,11 @@ export class WidgetVerification { if (props.loading) return ; - if (props.showPartnerModal) { - return ( - - ); - } - const style = { Dialog: { "&::part(panel)": { maxWidth: "480px", }, - "&::part(title)": { - fontSize: "var(--sl-font-size-x-large)", - fontWeight: "600", - padding: - "var(--sl-spacing-x-large) var(--sl-spacing-x-large) 0 var(--sl-spacing-x-large)", - }, "&::part(body)": { padding: "0 var(--sl-spacing-x-large)", fontSize: "var(--sl-font-size-small)", @@ -294,74 +286,72 @@ export class WidgetVerification { "&::part(overlay)": { background: "rgba(0, 0, 0, 0.5)", }, - "&::part(close-button)": { - display: "none", - }, + }, + 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(); - const generalText = this.getStepTextProps("general_"); + // const generalText = this.getStepTextProps("general_"); + const partnerText = this.getStepTextProps("createPartnerStep_"); + + const dialogLabel = this.general_widgetHeaderWithPartnerCreation; + + const renderStepContent = () => { + if (props.showPartnerModal) { + return ( + + ); + } + if (props.showCode) { + return ( + + ); + } + return ( + + ); + }; return (
-

- {generalText.verifyEmailHeader} -

-

{ + e.preventDefault(); }} - > - {generalText.verifyEmailDescription} -

- {props.showCode ? ( - { - e.preventDefault(); - }} - onSl-hide={(e: any) => { - if (e.target?.tagName === "SL-DIALOG") { - e.preventDefault(); - } - }} - > - - - ) : ( - { + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { e.preventDefault(); - }} - onSl-hide={(e: any) => { - if (e.target?.tagName === "SL-DIALOG") { - e.preventDefault(); - } - }} - > - - - )} + } + }} + > +

{dialogLabel}

+ {renderStepContent()} +
); } From 30f702732f7bab450f97783907eb6ae1fa5a2fee Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Tue, 14 Apr 2026 11:26:36 -0700 Subject: [PATCH 12/64] added new banking form fields --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 36 ++ .../sqm-banking-info-form/formDefinitions.tsx | 38 ++ .../sqm-banking-info-form/readme.md | 2 + .../sqm-banking-info-form.tsx | 10 + .../useBankingInfoForm.tsx | 2 + .../tax-and-cash/sqm-tax-and-cash/readme.md | 388 +++++++++--------- .../sqm-tax-and-cash/sqm-tax-and-cash.tsx | 12 + .../src/templates/TaxAndCash.html | 2 + 9 files changed, 298 insertions(+), 194 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 04b3502f0e..f14925097b 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.1.8", + "version": "2.1.10-0", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index 326011f4ad..432dc994c1 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -117,6 +117,10 @@ export namespace Components { * @uiName Branch code field label */ "branchCodeLabel": string; + /** + * @uiName Branch name field label + */ + "branchNameLabel": string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -130,6 +134,10 @@ export namespace Components { * @uiName Classification CPF field label */ "classificationCPFLabel": string; + /** + * @uiName Classification code field label + */ + "classificationCodeLabel": string; /** * @uiName Classification entity field label */ @@ -6110,6 +6118,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 @@ -6126,6 +6139,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 @@ -8044,6 +8062,10 @@ declare namespace LocalJSX { * @uiName Branch code field label */ "branchCodeLabel"?: string; + /** + * @uiName Branch name field label + */ + "branchNameLabel"?: string; /** * One of three options listed for the classification field * @uiName Business classification option @@ -8057,6 +8079,10 @@ declare namespace LocalJSX { * @uiName Classification CPF field label */ "classificationCPFLabel"?: string; + /** + * @uiName Classification code field label + */ + "classificationCodeLabel"?: string; /** * @uiName Classification entity field label */ @@ -14008,6 +14034,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 @@ -14024,6 +14055,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 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..f815e0d4d7 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 @@ -415,5 +415,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..5f4e5582fc 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 @@ -20,9 +20,11 @@ | `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"` | +| `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"` | +| `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"` | 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..c80eef88db 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 */ 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..dd6f86e3fe 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 @@ -79,6 +79,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; 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..b79a2682df 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,201 @@ ## 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_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_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_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_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..88af3c1ccf 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 @@ -557,6 +557,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 +570,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 diff --git a/packages/mint-components/src/templates/TaxAndCash.html b/packages/mint-components/src/templates/TaxAndCash.html index e90c98da6a..fc628eee15 100644 --- a/packages/mint-components/src/templates/TaxAndCash.html +++ b/packages/mint-components/src/templates/TaxAndCash.html @@ -266,7 +266,9 @@

Activity

step-4_vo-code-label="VO code" step-4_agency-code-label="Agency code" step-4_branch-code-label="Branch code" + step-4_branch-name-label="Branch name" step-4_classification-label="Classification" + step-4_classification-code-label="KBE code" step-4_tax-payer-id-label="Beneficiary INN" step-4-bank-address-label="Bank address" step-4_bank-city-label="Bank city" From c89ba11cb8196b2404505a988599ab1f60932983 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Tue, 14 Apr 2026 11:30:29 -0700 Subject: [PATCH 13/64] hygiene --- packages/mint-components/CHANGELOG.md | 11 ++++++++++- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/mint-components/CHANGELOG.md b/packages/mint-components/CHANGELOG.md index 17566be701..f685e92724 100644 --- a/packages/mint-components/CHANGELOG.md +++ b/packages/mint-components/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.1.9] - 2026-04-14 + +### Updated + +- \ +- \ + - Added support for newly added fields `classificationCode` and `branchName` + ## [2.1.8] - 2026-04-09 ### Fixed @@ -1536,7 +1544,8 @@ This major release represents a significant advancement in the theming capabilit - \ - \ -[unreleased]: https://github.com/saasquatch/program-tools/compare/mint-components@2.1.8...HEAD +[unreleased]: https://github.com/saasquatch/program-tools/compare/mint-components@2.1.9...HEAD +[2.1.9]: https://github.com/saasquatch/program-tools/releases/tag/%40saasquatch%2Fmint-components%402.1.9 [2.1.8]: https://github.com/saasquatch/program-tools/releases/tag/%40saasquatch%2Fmint-components%402.1.8 [2.1.7]: https://github.com/saasquatch/program-tools/releases/tag/%40saasquatch%2Fmint-components%402.1.7 [2.1.6]: https://github.com/saasquatch/program-tools/releases/tag/%40saasquatch%2Fmint-components%402.1.6 diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index 9d1939694b..e71febbb09 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.1.8", + "version": "2.1.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.1.8", + "version": "2.1.9", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index f14925097b..1f2df8a22d 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.1.10-0", + "version": "2.1.9", "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", From 462180fe2796510607f00597850320ee10b591b2 Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 14 Apr 2026 11:39:18 -0700 Subject: [PATCH 14/64] Fix content editor state previews and styling issues --- packages/mint-components/package.json | 2 +- .../sqm-partner-info-modal-view.tsx | 194 +++++++++--------- .../sqm-partner-info-modal.tsx | 9 +- .../sqm-widget-verification.tsx | 15 +- 4 files changed, 116 insertions(+), 104 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index d657cbdac0..b5dcd172a6 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.1.9-13", + "version": "2.1.9-17", "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", 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 index 9c992e14eb..853454ffd4 100644 --- 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 @@ -49,31 +49,21 @@ const style = { fontSize: "var(--sl-font-size-small)", overflow: "visible", }, - "&::part(footer)": { - display: "flex", - flexDirection: "column", - padding: - "0 var(--sl-spacing-large) var(--sl-spacing-large) var(--sl-spacing-large)", - }, "&::part(overlay)": { - background: "rgba(0, 0, 0, 0.5)", + background: "var(--sl-overlay-background-color)", }, }, DialogTitle: { fontSize: "var(--sl-font-size-x-large)", fontWeight: "600", - padding: "var(--sl-spacing-x-large) 0 0 0", + padding: "var(--sl-spacing-large) 0 0 0", margin: "0", }, FormFields: { display: "flex", flexDirection: "column", gap: "var(--sl-spacing-medium)", - marginTop: "var(--sl-spacing-medium)", - - "& > *": { - flex: 1, - }, + marginTop: "var(--sl-spacing-large)", }, ErrorMessage: { color: "var(--sqm-danger-color-text, #d32f2f)", @@ -98,7 +88,7 @@ const style = { }, SubmitButton: { width: "100%", - marginTop: "var(--sl-spacing-small)", + marginTop: "var(--sl-spacing-large)", }, }; @@ -122,102 +112,110 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { ? text.confirmButtonLabel : text.submitButtonLabel; - return [ - , - description, -
- - e.stopPropagation()} - onSl-input={(e: any) => { - callbacks.setCountrySearch(e.target?.value); - }} - /> - {states.filteredCountries?.map((c) => ( - {c.displayName} - ))} - + 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} + ))} + +
+ {states.error &&

{states.error}

} + - e.stopPropagation()} - onSl-input={(e: any) => callbacks.setCurrencySearch(e.target?.value)} - /> - {states.filteredCurrencies?.map((c) => ( - {c.currencyCode} - ))} - -
, - states.error &&

{states.error}

, - - {buttonLabel} - , - ]; + {buttonLabel} + +
+ ); } export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { - const { states, text } = props; + const { states, text, callbacks } = props; const sheet = createStyleSheet(style); + const styleString = sheet.toString(); console.log(states, "partner info modal states"); // TEMP if (!states.open) return
; return ( - { - e.preventDefault(); - }} - onSl-hide={(e: any) => { - if (e.target?.tagName === "SL-DIALOG") { - e.preventDefault(); +
+ + -

- {states.isExistingPartner - ? text.modalHeaderExistingPartner - : text.modalHeader} -

- -
+ onSl-request-close={(e: any) => { + e.preventDefault(); + }} + onSl-hide={(e: any) => { + if (e.target?.tagName === "SL-DIALOG") { + e.preventDefault(); + } + }} + > +

+ {states.isExistingPartner + ? text.modalHeaderExistingPartner + : text.modalHeader} +

+ + +
); } 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 index 49f9a78655..00ad0ea7a9 100644 --- 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 @@ -3,6 +3,7 @@ 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, @@ -17,7 +18,7 @@ import { /** * @uiName Partner Info Modal * @exampleGroup Tax and Cash - * @validParents ["sqm-portal-container", "sqm-portal-frame", "div", "sqb-program-section", "sqb-conditional-section"] + * @validParents ["sqm-portal-container", "sqm-portal-frame", "div", "sqm-divided-layout", "sqb-program-section", "sqb-conditional-section"] * @example Partner Info Modal - */ @Component({ @@ -180,6 +181,10 @@ function useDemoPartnerInfoModal( const [currency, setCurrency] = useState(""); const [error, setError] = useState(""); + const parsed = parseStates(props.stateController); + const stateOverride = + parsed?.["sqm-partner-info-modal"] || parsed || {}; + // @ts-ignore return deepmerge( { @@ -234,7 +239,7 @@ function useDemoPartnerInfoModal( modalHeaderExistingPartner: props.modalHeaderExistingPartner, }, }, - props.demoData || props.stateController || {}, + props.demoData || stateOverride, { arrayMerge: (_, a) => a }, ); } 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 48152c676d..6d4b4baded 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 @@ -268,6 +268,15 @@ export class WidgetVerification { if (props.loading) return ; + // 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
; + } + const style = { Dialog: { "&::part(panel)": { @@ -309,9 +318,9 @@ export class WidgetVerification { ); } From c8f126afa7e6a60238ca46b62259bf535fdaa80b Mon Sep 17 00:00:00 2001 From: Andy Date: Tue, 14 Apr 2026 16:17:34 -0700 Subject: [PATCH 15/64] Update useUserInfoForm and useIndirectTaxForm to include completeImpactPartner mutation --- packages/mint-components/package.json | 2 +- .../useIndirectTaxForm.tsx | 81 +++++++++++++++---- .../sqm-user-info-form/useUserInfoForm.tsx | 43 +++++++--- 3 files changed, 97 insertions(+), 29 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index b5dcd172a6..bb17fe77a5 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.1.9-17", + "version": "2.1.9-18", "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", 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..a720b56bab 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,10 @@ export type ConnectPartnerResult = { } | null; }; }; + +export type CompletePartnerResult = { + completeImpactConnection: ConnectPartnerResult["createImpactConnection"]; +}; type ImpactConnectionInput = { user: { id: string; @@ -104,6 +108,33 @@ 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 { + brandedSignup + requiredTaxDocumentType + currentTaxDocument { + type + status + } + } + } + } + } + } +`; + function getOption(countries: TaxCountry[] | undefined, countryCode: string) { if (!countries) return; @@ -127,6 +158,10 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { connectImpactPartner, { loading: connectLoading, errors: connectErrors }, ] = useMutation(CONNECT_PARTNER); + const [ + completeImpactPartner, + { loading: completeLoading, errors: completeErrors }, + ] = useMutation(COMPLETE_PARTNER); const userForm = useParentValue(USER_FORM_CONTEXT_NAMESPACE); const { data: userData, @@ -141,9 +176,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 +203,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { const _option = getOption( _countries, - publisher.taxInformation.indirectTaxCountryCode + publisher.taxInformation.indirectTaxCountryCode, ); setOption(_option); }, [publisher, _countries]); @@ -179,8 +214,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 +279,29 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { withholdingTaxId: formData.subRegionTaxNumber, } as ImpactConnectionInput; - const result = await connectImpactPartner({ - vars, - }); + //AL: TODO completePartnerMutation might change + let result = null; + let connectionResult; + if (userData?.user?.impactConnection?.connected) { + 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 +309,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 +355,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,8 +388,9 @@ 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, loadingError: !!userError?.message, formState: { diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index cac86cdb83..ca0150fbb5 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -33,7 +33,9 @@ import { ImpactConnection } from "../../../saasquatch"; import { TAX_FORM_UPDATED_EVENT_KEY } from "../eventKeys"; import { ConnectPartnerResult, + CompletePartnerResult, CONNECT_PARTNER, + COMPLETE_PARTNER, } from "../sqm-indirect-tax-form/useIndirectTaxForm"; import { gql } from "graphql-request"; @@ -94,6 +96,11 @@ export function useUserInfoForm(props: TaxForm) { { loading: connectLoading, errors: connectErrors }, ] = useMutation(CONNECT_PARTNER); + const [ + completeImpactPartner, + { loading: completeLoading, errors: completeErrors }, + ] = useMutation(COMPLETE_PARTNER); + const { data: tenantData } = useQuery(GET_INDIRECT_TAX_COUNTRY_CODE, {}); const { @@ -131,6 +138,7 @@ export function useUserInfoForm(props: TaxForm) { useEffect(() => { const user = data?.user; + console.log(user, "data from user query namesapce"); if (!user || step !== "/1") return; // If form already filled out, skip initialising it @@ -255,17 +263,31 @@ export function useUserInfoForm(props: TaxForm) { phoneNumberCountryCode: formData.phoneNumberCountryCode, } as Partial; - const result = await connectImpactPartner({ - vars, - }); + //AL: TODO completePartnerMutation might change + // if user already has an impact connection, call completeImpactPartner to update their information instead of connectPartner + const userData = data?.user; + let result = null; + let connectionResult; + if (userData?.impactConnection?.connected) { + console.log(vars, "values for completeImpactPartner"); + 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) { - // Output backend errors to console for now + if (!connectionResult?.success) { console.error( "Failed to create Impact connection: ", - (result as ConnectPartnerResult).createImpactConnection - .validationErrors, + connectionResult?.validationErrors, ); throw new Error(); @@ -273,8 +295,7 @@ export function useUserInfoForm(props: TaxForm) { await refetch(); - const resultPublisher = (result as ConnectPartnerResult) - .createImpactConnection?.user?.impactConnection?.publisher; + const resultPublisher = connectionResult?.user?.impactConnection?.publisher; const hasValidCurrentDocument = validTaxDocument(resultPublisher?.requiredTaxDocumentType) && @@ -421,9 +442,9 @@ export function useUserInfoForm(props: TaxForm) { step: step?.replace("/", ""), hideState: !hasStates, hideSteps: !!context.hideSteps, - disabled: loading || connectLoading, + disabled: loading || connectLoading || completeLoading, loadingError: !!userError?.message, - loading: loading || connectLoading, + loading: loading || connectLoading || completeLoading, isPartner: !!data?.user?.impactConnection?.publisher, isUser: !!data?.user?.impactConnection?.user, From 56dd0453ca6ecbfedb0541f958c1b0202468eeda Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 15 Apr 2026 16:52:02 -0700 Subject: [PATCH 16/64] Add currency filtering based on country - similiar to user info form --- .../sqm-partner-info-modal-view.tsx | 4 +- .../usePartnerInfoModal.tsx | 48 +++++++++++++++---- .../sqm-user-info-form/useUserInfoForm.tsx | 1 - 3 files changed, 41 insertions(+), 12 deletions(-) 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 index 853454ffd4..78f33617fd 100644 --- 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 @@ -159,7 +159,9 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { } /> {states.filteredCurrencies?.map((c) => ( - {c.currencyCode} + + {c.currencyCode} - {c.displayName} + ))}
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 index b62cbc78eb..27963a507e 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -5,7 +5,7 @@ import { useSetParent, useUserIdentity, } from "@saasquatch/component-boilerplate"; -import { useEffect, useState } from "@saasquatch/universal-hooks"; +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"; @@ -13,6 +13,10 @@ import { ConnectPartnerResult } from "../tax-and-cash/sqm-indirect-tax-form/useI import { validTaxDocument } from "../tax-and-cash/utils"; import { TAX_FORM_UPDATED_EVENT_KEY } from "../tax-and-cash/eventKeys"; import { VERIFICATION_PARENT_NAMESPACE } from "../sqm-widget-verification/keys"; +import { + GET_FINANCE_NETWORK_SETTINGS, + FinanceNetworkSettingsQuery, +} from "../tax-and-cash/data"; // new field under impactConnection:{ resolvedByEmail: boolean } - determines if connection came from managed identity export const GET_USER_PARTNER_INFO = gql` @@ -152,18 +156,47 @@ export function usePartnerInfoModal( {}, ); + const { data: financeNetworkData } = useQuery( + GET_FINANCE_NETWORK_SETTINGS, + { + variables: { filter: {} }, + }, + ); + const [ connectImpactPartner, { loading: connectLoading, errors: connectErrors }, ] = useMutation(CONNECT_PARTNER); const [countryCode, setCountryCode] = useState( - user?.impactConnection?.publisher?.countryCode || "", + user?.impactConnection?.publisher?.countryCode || "US", ); const [currency, setCurrency] = useState( user?.impactConnection?.publisher?.currency || "", ); + const countries = countriesData?.impactPayoutCountries?.data || []; + + // copied from useTaxAndCash for displaying currencies based on country - could be in helper? + const currencies = useMemo(() => { + const allValidCurrencies = + financeNetworkData?.impactFinanceNetworkSettings?.data?.reduce( + (agg, settings) => { + const currency = currenciesData?.currencies?.data?.find( + (c) => c.currencyCode === settings.currency, + ); + if (!currency) return agg; + if (agg.find((c) => c.currencyCode === settings.currency)) return agg; + if (countryCode && settings.countryCode !== countryCode) return agg; + return [...agg, currency]; + }, + [], + ); + return (allValidCurrencies || []).sort((a, b) => + a.displayName.localeCompare(b.displayName), + ); + }, [financeNetworkData, currenciesData, countryCode]); + console.log(countryCode, currency, "initial country and currency state"); // TEMP const [countrySearch, setCountrySearch] = useState(""); const [currencySearch, setCurrencySearch] = useState(""); @@ -171,17 +204,12 @@ export function usePartnerInfoModal( countriesData?.impactPayoutCountries?.data || [], ); const [filteredCurrencies, setFilteredCurrencies] = useState( - currenciesData?.currencies?.data || [], + currencies || [], ); const [error, setError] = useState(""); const [success, setSuccess] = useState(false); - const countries = countriesData?.impactPayoutCountries?.data || []; - const currencies = currenciesData?.currencies?.data || []; - - console.log(user, "user data from partner info query"); // TEMP - useEffect(() => { if (userData && user.impactConnection?.publisher) { setCountryCode(user.impactConnection.publisher.countryCode); @@ -292,8 +320,6 @@ export function usePartnerInfoModal( } } - console.log(success, "success state in partner info modal"); - const showModal = !success && !userLoading && @@ -301,6 +327,8 @@ export function usePartnerInfoModal( console.log(showModal, "showModal condition in partner info modal"); // TEMP + console.log(filteredCurrencies, "filtered curricneis"); + return { states: { open: showModal, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index ca0150fbb5..aace0ca7bf 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -138,7 +138,6 @@ export function useUserInfoForm(props: TaxForm) { useEffect(() => { const user = data?.user; - console.log(user, "data from user query namesapce"); if (!user || step !== "/1") return; // If form already filled out, skip initialising it From c119d466b367c932199d7c619724a1b234f05627 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 16 Apr 2026 13:35:06 -0700 Subject: [PATCH 17/64] Pre-fill country in partner modal based on local. Update step logic to handle country that has states. --- packages/mint-components/package.json | 2 +- .../sqm-partner-info-modal-view.tsx | 2 - .../usePartnerInfoModal.tsx | 8 ++- .../sqm-widget-verification.tsx | 60 +++++++++---------- .../sqm-tax-and-cash/useTaxAndCash.tsx | 9 ++- 5 files changed, 45 insertions(+), 36 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index bb17fe77a5..fd8fba1fab 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.1.9-18", + "version": "2.1.9-19", "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", 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 index 78f33617fd..dbf7f497f6 100644 --- 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 @@ -118,7 +118,6 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) {
{description} (CONNECT_PARTNER); + // No pre-filled country, use locale to determine countryCode instead const [countryCode, setCountryCode] = useState( - user?.impactConnection?.publisher?.countryCode || "US", + user?.impactConnection?.publisher?.countryCode || + locale.replace(/^.*_/, ""), ); + const [currency, setCurrency] = useState( user?.impactConnection?.publisher?.currency || "", ); @@ -207,6 +211,8 @@ export function usePartnerInfoModal( currencies || [], ); + console.log(userData, "userData partner info modal"); + const [error, setError] = useState(""); const [success, setSuccess] = useState(false); 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 6d4b4baded..bac965346d 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 @@ -17,6 +17,36 @@ import { 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 * @exampleGroup Widget Verification @@ -277,36 +307,6 @@ export class WidgetVerification { return
; } - 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(); - // const generalText = this.getStepTextProps("general_"); const partnerText = this.getStepTextProps("createPartnerStep_"); 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 2a7df7ae10..0327d95c4e 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 @@ -49,15 +49,20 @@ function getCurrentStep(user: UserQuery["user"]) { billingCity, billingPostalCode, billingState, + countryCode, } = user.impactConnection.publisher; - console.log(phoneNumber, "phone number in getCurrentStep"); + console.log( + user.impactConnection.publisher, + "publisher data in useTaxAndCash to determine step", + ); // Early partner creation does not collect these fields + const stateRequired = ["ES", "AU", "US", "CA"].includes(countryCode); if ( !billingAddress || !billingCity || !billingPostalCode || - !billingState || + (stateRequired && !billingState) || !phoneNumber ) { return "/1"; From 889f58a61a37911404c4b9b0fee9d4f468b489ad Mon Sep 17 00:00:00 2001 From: Zach Harrison Date: Fri, 17 Apr 2026 14:36:00 -0700 Subject: [PATCH 18/64] Fix CSS part clashing by giving non shadow DOM component a very specific part name --- .../src/components/sqm-text-span/sqm-text-span-view.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mint-components/src/components/sqm-text-span/sqm-text-span-view.tsx b/packages/mint-components/src/components/sqm-text-span/sqm-text-span-view.tsx index 472eaa27d4..49b97713c5 100644 --- a/packages/mint-components/src/components/sqm-text-span/sqm-text-span-view.tsx +++ b/packages/mint-components/src/components/sqm-text-span/sqm-text-span-view.tsx @@ -62,7 +62,7 @@ export function TextSpanView(props: TextSpanViewProps, children: VNode) { `; return ( - + @@ -162,13 +188,34 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { ))}
+
+ + {bankingCollectionText} + + {states.checkboxError && ( +

{states.checkboxError}

+ )} +
{states.error &&

{states.error}

} { @@ -213,6 +234,9 @@ function useDemoPartnerInfoModal( const value = e?.detail?.item?.__value; if (value) setCurrency(value); }, + onCheckboxChange: (e: any) => { + setAllowBankingCollection(e.target.checked); + }, setCountrySearch: () => {}, setCurrencySearch: () => {}, onSubmit: () => { 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 index 2948f29677..9272d06c14 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -188,6 +188,8 @@ export function usePartnerInfoModal( { loading: connectLoading, errors: connectErrors }, ] = useMutation(START_IMPACT_CONNECTION); + const [allowBankingCollection, setAllowBankingCollection] = useState(false); + const [checkboxError, setCheckboxError] = useState(""); // No pre-filled country, use locale to determine countryCode instead const [countryCode, setCountryCode] = useState( user?.impactConnection?.publisher?.countryCode || @@ -284,12 +286,23 @@ export function usePartnerInfoModal( setError(""); } + function onCheckboxChange(e: any) { + const checked = e.target.checked; + setAllowBankingCollection(checked); + if (checked) setCheckboxError(""); + } + async function onSubmit() { + if (!allowBankingCollection) { + setCheckboxError(props.missingFieldsErrorText); + return; + } if (!countryCode || !currency) { setError(props.missingFieldsErrorText); return; } setError(""); + setCheckboxError(""); try { const vars = { @@ -362,10 +375,14 @@ export function usePartnerInfoModal( brandName: tenantSettingsData?.tenantSettings?.companyName || "", filteredCountries: filteredCountries || [], filteredCurrencies: filteredCurrencies || [], + allowBankingCollection, + checkboxError, + disabled: userLoading || connectLoading, }, callbacks: { onCountryChange, onCurrencyChange, + onCheckboxChange, setCurrencySearch, setCountrySearch, onSubmit, 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 dc3771b783..723dcf0574 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 @@ -885,7 +885,8 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { ))} -
+ {/*
+ AL: FLAGGED FOR DELETION { @@ -907,7 +908,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { )}

)} -
+
*/}
Date: Tue, 5 May 2026 15:09:09 -0700 Subject: [PATCH 21/64] Bump version to MINOR. Remove defaulting country in partner creation modal. Update story naming to match designs --- packages/mint-components/package.json | 2 +- .../sqm-partner-info-modal-view.tsx | 13 ++++++++---- .../usePartnerInfoModal.tsx | 5 +---- .../TaxAndCashReferralTable.stories.tsx | 5 +++-- ...ndCashReferralTableRewardsCell.stories.tsx | 21 ++++++++++++++++++- .../TaxAndCashRewardsTable.stories.tsx | 4 ++-- .../TaxAndCashRewardsTableCell.stories.tsx | 20 +++++++++++++++++- packages/mint-components/src/saasquatch.d.ts | 1 + 8 files changed, 56 insertions(+), 15 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 17b002aab2..80d3065782 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.1.9-21", + "version": "2.2.0-0", "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", 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 index 0c0bfaaf9e..14051e6ae5 100644 --- 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 @@ -84,6 +84,9 @@ const style = { borderRadius: "0", }, }, + SelectMenu: { + maxHeight: "300px", + }, DescriptionContainer: { display: "flex", flexDirection: "column", @@ -144,7 +147,7 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) {
{description} - {states.filteredCountries?.map((c) => ( - {c.displayName} - ))} +
+ {states.filteredCountries?.map((c) => ( + {c.displayName} + ))} +
, , , - , + , ], [ , 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..1482d36a80 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 @@ -231,7 +231,26 @@ export const PendingTaxSubmission = () => { ); }; -export const PendingPartnerCreation = () => { +export const PartnerNotCreatedSetupNotStarted = () => { + return ( + + ); +}; + +export const PartnerCreatedSetupStartedButIncomplete = () => { return ( , - , + , , , ]; 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..4c16fb9c61 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 @@ -102,6 +102,7 @@ const processingPFT = { const taxConnection: ImpactConnection = { connected: true, taxHandlingEnabled: true, + connectionStatus: "NOT_STARTED", publisher: { requiredTaxDocumentType: "W8BEN", currentTaxDocument: { @@ -190,7 +191,24 @@ export const StatusCellPendingTaxSubmission = () => { ); }; -export const StatusCellPendingPartnerCreation = () => { +export const StatusCellPartnerCreatedSetupStartedButIncomplete = () => { + return ( + + ); +}; + +export const StatusCellPartnerNotCreatedSetupNotStarted = () => { return ( Date: Wed, 6 May 2026 14:06:43 -0700 Subject: [PATCH 22/64] Remove unused state checkboxerror, some cleanup --- packages/mint-components/package.json | 2 +- .../PartnerInfoModal.stories.tsx | 1 - .../sqm-partner-info-modal-view.tsx | 29 +++++++++++-------- .../sqm-partner-info-modal.tsx | 1 - .../usePartnerInfoModal.tsx | 20 ++----------- .../sqm-tax-and-cash/useTaxAndCash.tsx | 4 --- .../sqm-user-info-form/useUserInfoForm.tsx | 3 +- 7 files changed, 21 insertions(+), 39 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 80d3065782..9fe9fd7924 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-0", + "version": "2.2.0-1", "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", 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 index 59bc2dd3ac..31333ab89e 100644 --- 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 @@ -73,7 +73,6 @@ const defaultProps: PartnerInfoModalViewProps = { filteredCountries: demoCountries, filteredCurrencies: demoCurrencies, allowBankingCollection: false, - checkboxError: "", disabled: false, }, callbacks: noopCallbacks, 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 index 14051e6ae5..e6472fa68d 100644 --- 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 @@ -16,7 +16,6 @@ export interface PartnerInfoModalViewProps { filteredCountries: { countryCode: string; displayName: string }[]; filteredCurrencies: { currencyCode: string; displayName: string }[]; allowBankingCollection: boolean; - checkboxError: string; disabled: boolean; }; callbacks: { @@ -73,8 +72,6 @@ const style = { marginTop: "var(--sl-spacing-large)", }, ErrorMessage: { - color: "var(--sqm-danger-color-text, #d32f2f)", - fontSize: "var(--sl-font-size-small)", marginTop: "var(--sl-spacing-x-small)", }, SearchInput: { @@ -187,11 +184,13 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { callbacks.setCurrencySearch(e.target?.value) } /> - {states.filteredCurrencies?.map((c) => ( - - {c.currencyCode} - {c.displayName} - - ))} +
+ {states.filteredCurrencies?.map((c) => ( + + {c.currencyCode} - {c.displayName} + + ))} +
{bankingCollectionText} - {states.checkboxError && ( -

{states.checkboxError}

- )}
- {states.error &&

{states.error}

} + {states.error && ( +
+ + {/*

{text.error.loadingErrorAlertHeader}

*/} +

{states.error}

+
+
+ )} + + {/* {states.error &&

{states.error}

} */} (START_IMPACT_CONNECTION); const [allowBankingCollection, setAllowBankingCollection] = useState(false); - const [checkboxError, setCheckboxError] = useState(""); // No pre-filled country, use locale to determine countryCode instead const [countryCode, setCountryCode] = useState( user?.impactConnection?.publisher?.countryCode || "", @@ -286,12 +281,11 @@ export function usePartnerInfoModal( function onCheckboxChange(e: any) { const checked = e.target.checked; setAllowBankingCollection(checked); - if (checked) setCheckboxError(""); } async function onSubmit() { if (!allowBankingCollection) { - setCheckboxError(props.missingFieldsErrorText); + setError(props.missingFieldsErrorText); return; } if (!countryCode || !currency) { @@ -299,7 +293,6 @@ export function usePartnerInfoModal( return; } setError(""); - setCheckboxError(""); try { const vars = { @@ -309,8 +302,6 @@ export function usePartnerInfoModal( }, firstName: user.firstName, lastName: user.lastName, - // phoneNumber: null, - // phoneNumberCountryCode: null, countryCode, currency, }; @@ -325,12 +316,6 @@ export function usePartnerInfoModal( const connectionResult = (result as StartImpactConnectionResult) .startImpactConnection; - console.log( - result, - connectionResult, - "result and connectionResult from creating partner from modal", - ); - if (!connectionResult?.success) { const validationMsg = connectionResult?.validationErrors ?.map((e) => e.message) @@ -373,7 +358,6 @@ export function usePartnerInfoModal( filteredCountries: filteredCountries || [], filteredCurrencies: filteredCurrencies || [], allowBankingCollection, - checkboxError, disabled: userLoading || connectLoading, }, callbacks: { 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 d5fae0a6d8..e545f65146 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 @@ -45,10 +45,6 @@ function getCurrentStep(user: UserQuery["user"]) { brandedSignup, payoutsAccount, } = user.impactConnection.publisher; - console.log( - user.impactConnection, - "publisher data in useTaxAndCash to determine step new new", - ); if (user.impactConnection.connectionStatus === "STARTED") { return "/1"; } diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index 18f2194f21..3c39d3eea5 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -156,7 +156,7 @@ export function useUserInfoForm(props: TaxForm) { lastName: user.impactConnection.user.lastName, countryCode: user.impactConnection.publisher.countryCode, currency: user.impactConnection.publisher.currency, - // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "000000" and the phoneNumberCountryCode to "DZ" + // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "0000000" and the phoneNumberCountryCode to "DZ" phoneNumberCountryCode: user.impactConnection.publisher.phoneNumber === "0000000" ? null @@ -268,7 +268,6 @@ export function useUserInfoForm(props: TaxForm) { let result = null; let connectionResult; if (userData?.impactConnection?.connectionStatus === "STARTED") { - console.log(vars, "values for completeImpactPartner"); result = await completeImpactPartner({ vars, }); From cfbfad94bfc8a1291a97a574a562d6a84d91baf3 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Wed, 6 May 2026 14:25:30 -0700 Subject: [PATCH 23/64] a bunch of review and cleanup of unused code --- .../sqm-partner-info-modal-view.tsx | 3 -- .../sqm-partner-info-modal.tsx | 9 ---- .../usePartnerInfoModal.tsx | 39 ++++---------- .../sqm-widget-verification/keys.ts | 2 + .../sqm-widget-verification.tsx | 14 ----- .../useWidgetVerification.ts | 32 +++++++++--- .../useIndirectTaxForm.tsx | 17 +++---- .../sqm-user-info-form-view.tsx | 51 +------------------ .../sqm-user-info-form/useUserInfoForm.tsx | 16 +++--- 9 files changed, 51 insertions(+), 132 deletions(-) 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 index e6472fa68d..4699d1a186 100644 --- 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 @@ -12,7 +12,6 @@ export interface PartnerInfoModalViewProps { currency: string; error: string; success: boolean; - brandName: string; filteredCountries: { countryCode: string; displayName: string }[]; filteredCurrencies: { currencyCode: string; displayName: string }[]; allowBankingCollection: boolean; @@ -209,13 +208,11 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { {states.error && (
- {/*

{text.error.loadingErrorAlertHeader}

*/}

{states.error}

)} - {/* {states.error &&

{states.error}

} */} ( - GET_BRAND_NAME, - {}, - ); - const { data: financeNetworkData } = useQuery( GET_FINANCE_NETWORK_SETTINGS, { @@ -179,10 +160,8 @@ export function usePartnerInfoModal( }, ); - const [ - startImpactConnection, - { loading: connectLoading, errors: connectErrors }, - ] = useMutation(START_IMPACT_CONNECTION); + const [startImpactConnection, { loading: connectLoading }] = + useMutation(START_IMPACT_CONNECTION); const [allowBankingCollection, setAllowBankingCollection] = useState(false); // No pre-filled country, use locale to determine countryCode instead @@ -294,6 +273,11 @@ export function usePartnerInfoModal( } setError(""); + if (!user) { + setError(props.networkErrorText); + return; + } + try { const vars = { user: { @@ -332,7 +316,7 @@ export function usePartnerInfoModal( await refetch(); setSuccess(true); - setVerificationContext(true); + setPartnerCreated(true); } catch (e) { console.error("Partner creation error:", e); setError(props.networkErrorText); @@ -349,12 +333,11 @@ export function usePartnerInfoModal( open: showModal, loading: userLoading || countriesLoading || currenciesLoading, submitting: connectLoading, - isExistingPartner: impactConnection?.publisher, + isExistingPartner: !!impactConnection?.publisher, countryCode, currency, error, success, - brandName: tenantSettingsData?.tenantSettings?.companyName || "", filteredCountries: filteredCountries || [], filteredCurrencies: filteredCurrencies || [], allowBankingCollection, 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-widget-verification.tsx b/packages/mint-components/src/components/sqm-widget-verification/sqm-widget-verification.tsx index bac965346d..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 @@ -67,19 +67,6 @@ export class WidgetVerification { */ @Prop() general_widgetHeaderWithPartnerCreation = "Let's get you ready for rewards"; - /** - * @uiName General verify widget header text - * @uiGroup General Text - */ - @Prop() - general_verifyEmailHeader = "Verify your email"; - /** - * @uiName General verify widget description text - * @uiGroup General Text - */ - @Prop() - general_verifyEmailDescription = - "To get your cash paid out directly to your bank account, please complete your account setup"; /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ EMAIL STEP PROPS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ @@ -307,7 +294,6 @@ export class WidgetVerification { return
; } - // const generalText = this.getStepTextProps("general_"); const partnerText = this.getStepTextProps("createPartnerStep_"); const dialogLabel = this.general_widgetHeaderWithPartnerCreation; 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 0c58961cb3..689288cb7b 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -8,7 +8,9 @@ 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"; @@ -34,18 +36,21 @@ const USER_LOOKUP = gql` `; export function useWidgetVerification() { - const userIdentity = useUserIdentity(); - const [showCode, setShowCode] = useParentState({ + const [showCode] = useParentState({ namespace: SHOW_CODE_NAMESPACE, initialValue: false, }); - const [email, setEmail] = useParentState({ - namespace: VERIFICATION_EMAIL_NAMESPACE, - initialValue: userIdentity?.email, - }); const setContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); const [loading, setLoading] = useState(true); - const [showPartnerModal, setShowPartnerModal] = useState(false); + 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(() => { @@ -83,8 +88,19 @@ export function useWidgetVerification() { const onPartnerModalComplete = () => { setShowPartnerModal(false); + setPartnerCreated(false); setContext(true); }; - return { showCode, showPartnerModal, onVerification, onPartnerModalComplete, loading }; + useEffect(() => { + if (partnerCreated) onPartnerModalComplete(); + }, [partnerCreated]); + + return { + showCode, + showPartnerModal, + onVerification, + onPartnerModalComplete, + loading, + }; } 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 e76ab35cf2..1051fd2e2e 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 @@ -158,14 +158,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 [ - completeImpactPartner, - { loading: completeLoading, errors: completeErrors }, - ] = useMutation(COMPLETE_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, @@ -283,10 +279,11 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { withholdingTaxId: formData.subRegionTaxNumber, } as ImpactConnectionInput; - //AL: TODO completePartnerMutation might change + // 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?.connected) { + if (userData?.user?.impactConnection?.connectionStatus === "STARTED") { result = await completeImpactPartner({ vars, }); 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 723dcf0574..7bd16b48f5 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 @@ -431,7 +431,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { regionLabel = text.state; } - // when creating an impact connection without sending phoneNumber data, the impactAPI defaults the value to "000000" and the phoneNumberCountryCode to "DZ" + // 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" && @@ -530,30 +530,6 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => {

)} - {/* AL: Don't need to show this anymore due to early partner creation */} - {/* {(states.isPartner || states.isUser) && ( - -

{text.isPartnerAlertHeader}

-

- {intl.formatMessage( - { - id: "isPartnerAlertDescription", - defaultMessage: text.isPartnerAlertDescription, - }, - { - supportLink: ( - - {text.supportLink} - - ), - }, - )} -

-
- )} */}
@@ -884,31 +860,6 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { ))} - - {/*
- AL: FLAGGED FOR DELETION - { - 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, - )} -

- )} -
*/}
(CONNECT_PARTNER); + const [connectImpactPartner, { loading: connectLoading }] = + useMutation(CONNECT_PARTNER); - const [ - completeImpactPartner, - { loading: completeLoading, errors: completeErrors }, - ] = useMutation(COMPLETE_PARTNER); + const [completeImpactPartner, { loading: completeLoading }] = + useMutation(COMPLETE_PARTNER); const { data: tenantData } = useQuery(GET_INDIRECT_TAX_COUNTRY_CODE, {}); @@ -262,8 +258,8 @@ export function useUserInfoForm(props: TaxForm) { phoneNumberCountryCode: formData.phoneNumberCountryCode, } as Partial; - //AL: TODO completePartnerMutation might change - // if user already has an impact connection, call completeImpactPartner to update their information instead of connectPartner + // If the partner has already been started call completeImpactPartner to + // update the remaining details. Otherwise create a new connection. const userData = data?.user; let result = null; let connectionResult; From a9bd00286cac5fe6d284e7d5b853d5df3a12a257 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Wed, 6 May 2026 14:27:49 -0700 Subject: [PATCH 24/64] cleanup imports --- .../components/sqm-widget-verification/useWidgetVerification.ts | 2 -- 1 file changed, 2 deletions(-) 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 689288cb7b..1684880e69 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -2,7 +2,6 @@ import { useLazyQuery, useParentState, useSetParent, - useUserIdentity, } from "@saasquatch/component-boilerplate"; import { useState } from "@saasquatch/stencil-hooks"; import { useEffect } from "@saasquatch/universal-hooks"; @@ -11,7 +10,6 @@ import { PARTNER_CREATED_NAMESPACE, SHOW_CODE_NAMESPACE, SHOW_PARTNER_MODAL_NAMESPACE, - VERIFICATION_EMAIL_NAMESPACE, VERIFICATION_PARENT_NAMESPACE, } from "./keys"; From 6983a9eea5358210cc9519a7148857e73d5c5163 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Wed, 6 May 2026 14:53:38 -0700 Subject: [PATCH 25/64] fix function that doesnt exist in non-widget case --- .../components/sqm-partner-info-modal/usePartnerInfoModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 0e1a2f8be0..84f742bf6b 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -315,8 +315,8 @@ export function usePartnerInfoModal( window.dispatchEvent(new Event(TAX_FORM_UPDATED_EVENT_KEY)); await refetch(); + setPartnerCreated?.(true); setSuccess(true); - setPartnerCreated(true); } catch (e) { console.error("Partner creation error:", e); setError(props.networkErrorText); From 5b8b177b45ecff16b39400fd0905a631180dea14 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Thu, 7 May 2026 11:00:53 -0700 Subject: [PATCH 26/64] cleanup changelog entry --- packages/mint-components/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mint-components/CHANGELOG.md b/packages/mint-components/CHANGELOG.md index 231f7a94b7..cad10de90c 100644 --- a/packages/mint-components/CHANGELOG.md +++ b/packages/mint-components/CHANGELOG.md @@ -20,7 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - \ - - Added ability to customize share links when enabled in the editor + - Fixed issue where password field was changed to render in the shadow dom ## [2.1.10] - 2026-05-06 From 4094b7c7f41ebf523c21e5854c897bdb19f425dd Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 7 May 2026 13:10:31 -0700 Subject: [PATCH 27/64] Updates from deep dive 1 and added conditional to always show pending if withdrawal settings are not present in rewards table status column --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 48 --- .../src/components/sqm-form-message/readme.md | 2 + .../PartnerInfoModal.stories.tsx | 18 +- .../sqm-partner-info-modal/readme.md | 49 ++- .../sqm-partner-info-modal-view.tsx | 13 +- .../usePartnerInfoModal.tsx | 3 +- .../cells/sqm-referral-table-rewards-cell.tsx | 2 +- .../TaxAndCashRewardsTableCell.stories.tsx | 100 +++++ .../cells/sqm-rewards-table-status-cell.tsx | 24 +- ...qm-rewards-table-status-column-new.feature | 234 +++++++++++ .../sqm-rewards-table-status-column.feature | 40 +- .../src/components/sqm-stencilbook/readme.md | 1 + .../tax-and-cash/sqm-tax-and-cash/readme.md | 385 +++++++++--------- .../sqm-tax-and-cash/sqm-tax-and-cash.tsx | 12 +- .../UserInfoFormView.stories.tsx | 1 - .../tax-and-cash/sqm-user-info-form/readme.md | 1 - .../sqm-user-info-form-view.tsx | 2 - .../sqm-user-info-form/sqm-user-info-form.tsx | 7 +- 19 files changed, 632 insertions(+), 312 deletions(-) create mode 100644 packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 9fe9fd7924..c3d7aa4edb 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-1", + "version": "2.2.0-2", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index c773798d53..e23a0a9f66 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -2317,11 +2317,6 @@ export namespace Components { * @uiName Terms and conditions checkbox */ "allowBankingCollection": string; - /** - * Brand name shown in the modal header - * @uiName Brand name - */ - "brandName": string; /** * @uiName Confirm button label */ @@ -6007,11 +6002,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 @@ -7058,10 +7048,6 @@ export namespace Components { * @uiName Currency field label */ "currency": string; - /** - * @uiName Currency field help text - */ - "currencyHelpText": string; /** * @undocumented * @uiType object @@ -7353,16 +7339,6 @@ export namespace Components { * @uiGroup Email Verification Step */ "emailStep_verifyEmailHeaderText": string; - /** - * @uiName General verify widget description text - * @uiGroup General Text - */ - "general_verifyEmailDescription": string; - /** - * @uiName General verify widget header text - * @uiGroup General Text - */ - "general_verifyEmailHeader": string; /** * @uiName General widget header text with partner creation * @uiGroup General Text @@ -10543,11 +10519,6 @@ declare namespace LocalJSX { * @uiName Terms and conditions checkbox */ "allowBankingCollection"?: string; - /** - * Brand name shown in the modal header - * @uiName Brand name - */ - "brandName"?: string; /** * @uiName Confirm button label */ @@ -14209,11 +14180,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 @@ -15259,10 +15225,6 @@ declare namespace LocalJSX { * @uiName Currency field label */ "currency"?: string; - /** - * @uiName Currency field help text - */ - "currencyHelpText"?: string; /** * @undocumented * @uiType object @@ -15554,16 +15516,6 @@ declare namespace LocalJSX { * @uiGroup Email Verification Step */ "emailStep_verifyEmailHeaderText"?: string; - /** - * @uiName General verify widget description text - * @uiGroup General Text - */ - "general_verifyEmailDescription"?: string; - /** - * @uiName General verify widget header text - * @uiGroup General Text - */ - "general_verifyEmailHeader"?: string; /** * @uiName General widget header text with partner creation * @uiGroup General Text 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 c49c5aa8ec..22d16daa28 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) @@ -63,6 +64,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/PartnerInfoModal.stories.tsx b/packages/mint-components/src/components/sqm-partner-info-modal/PartnerInfoModal.stories.tsx index 31333ab89e..1c6f43c69f 100644 --- 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 @@ -65,11 +65,10 @@ const defaultProps: PartnerInfoModalViewProps = { loading: false, submitting: false, isExistingPartner: false, - countryCode: "", - currency: "", + countryCode: null, + currency: null, error: "", success: false, - brandName: "Test Brand", filteredCountries: demoCountries, filteredCurrencies: demoCurrencies, allowBankingCollection: false, @@ -120,6 +119,19 @@ export const ExistingPartnerConfirm = () => { return ; }; +export const ExistingPartnerEmpty = () => { + const props: PartnerInfoModalViewProps = { + ...defaultProps, + states: { + ...defaultProps.states, + isExistingPartner: true, + countryCode: "", + currency: "", + }, + }; + return ; +}; + export const Submitting = () => { const props: PartnerInfoModalViewProps = { ...defaultProps, 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 index fee2dee457..be3910479f 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -7,28 +7,27 @@ ## 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"` | -| `brandName` | `brand-name` | Brand name shown in the modal header | `string` | `""` | -| `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; brandName: string; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; allowBankingCollection: boolean; checkboxError: string; 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 select both a country and currency."` | -| `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` | `"{}"` | -| `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 Support or sign up for this referral program with a different email."` | -| `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"` | +| 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 select both a country and currency."` | +| `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` | `"{}"` | +| `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 Support or sign up for this referral program with a different email."` | +| `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 @@ -37,9 +36,15 @@ - [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-widget-verification --> sqm-partner-info-modal style sqm-partner-info-modal fill:#f9f,stroke:#333,stroke-width:4px ``` 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 index 4699d1a186..ed53a6b85f 100644 --- 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 @@ -97,6 +97,11 @@ const style = { justifyContent: "flex-start", flexDirection: "column", }, + Checkbox: { + "&::part(label)": { + fontSize: "var(--sl-font-size-small)", + }, + }, SubmitButton: { width: "100%", marginTop: "var(--sl-spacing-large)", @@ -146,7 +151,10 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { exportparts="label: input-label, base: input-base, menu: select-menu" label={text.countryLabel} value={states.countryCode} - disabled={states.submitting || !!states.isExistingPartner} + disabled={ + states.submitting || + (!!states.countryCode && states.isExistingPartner) + } required hoist onSl-select={callbacks.onCountryChange} @@ -170,7 +178,7 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { exportparts="label: input-label, base: input-base" label={text.currencyLabel} value={states.currency} - disabled={states.submitting || !!states.isExistingPartner} + disabled={states.submitting || !!states.currency} required hoist onSl-select={callbacks.onCurrencyChange} @@ -193,6 +201,7 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) {
)} - {state === "PAYOUT_APPROVED" && ( + {state === "" && (
{statusText}
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 4c16fb9c61..8dedec2ec5 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 @@ -305,3 +305,103 @@ export const StatusCellPayoutCancelled = () => { > ); }; + +export const StatusCellCancelled = () => { + return ( + + ); +}; + +export const StatusCellPFTNoWithdrawalSettings = () => { + return ( + + ); +}; + +export const StatusCellNotConnectedExistingTaxForm = () => { + return ( + + ); +}; + +export const StatusCellExistingPartnerTaxFormNoWithdrawalSettings = () => { + return ( + + ); +}; 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..521ed38bbb 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 @@ -101,16 +101,25 @@ 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 const fraudStatus = reward.referral?.fraudData?.moderationStatus; if (fraudStatus === "DENIED") return "DENIED"; if (fraudStatus === "PENDING") return "PENDING_REVIEW"; const partnerTransferStatus = reward.partnerFundsTransfer?.status; - + console.log(this.reward, "reward in status cell"); if (reward.partnerFundsTransfer) { if (partnerTransferStatus === "REVERSED") return "PAYOUT_CANCELLED"; if (partnerTransferStatus === "OVERDUE") return "PAYOUT_FAILED"; + } + + // pft can now be created before withdrawal settings exist + const needsPayoutSetup = + this.taxConnection?.connected && + !this.taxConnection?.publisher?.withdrawalSettings; + if (needsPayoutSetup) return "PENDING"; + if (reward.partnerFundsTransfer) { if ( reward.partnerFundsTransfer.dateScheduled && reward.partnerFundsTransfer.dateScheduled > Date.now() @@ -171,13 +180,24 @@ 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 ( + taxConnection?.connected && + !taxConnection?.publisher?.withdrawalSettings + ) { + return this.pendingPartnerCreation; + } + return ""; } diff --git a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature new file mode 100644 index 0000000000..bc528d5c24 --- /dev/null +++ b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature @@ -0,0 +1,234 @@ +@author:derek @owner:derek +Feature: Reward Table Status Column + Shows the status of each reward as a coloured pill plus a description + line. The status is derived from the reward's fraud state, the user's + Impact tax/payout connection, the reward's PartnerFundsTransfer (PFT), + the reward's lifecycle dates, and the reward's `statuses` / + `pendingReasons` arrays. + + Background: + Given the status column is included in the reward table + # ============================================================ + # 1. COLUMN CONFIGURATION + # ============================================================ + + @motivating @ui + Scenario Outline: The title of the status column is configurable + Given the "column-title" prop is set to + Then the status column is shown with + + Examples: + | value | columnTitle | + | Status | | + | My column title | My column title | + + @minutia @ui + Scenario Outline: Expiry status date text can be configured + Given the "expiry-text" prop has + And a user with an available reward with an expiry date + When they view the reward table + Then they see under the Available Status pill + + Examples: + | value | text | + | Expires | Expires | + | Redeem before | Redeem before | + # ============================================================ + # 2. STATUS PRECEDENCE + # rewardStatus() walks the rules below from top to bottom and the first + # matching rule wins. + # ============================================================ + + @motivating + Scenario: Status precedence ladder + Given a reward, its referral fraud state, the user's Impact tax connection + Then the displayed status is determined in the following order: + | order | rule | resulting status | resulting description | + | 1 | referral.fraudData.moderationStatus is "DENIED" | DENIED | Detected self-referral | + | 2 | referral.fraudData.moderationStatus is "PENDING" | PENDING_REVIEW | Awaiting review | + | 3 | partnerFundsTransfer.status is "REVERSED" | PAYOUT_CANCELLED | If you think this is a mistake, contact our Support team. | + | 4 | partnerFundsTransfer.status is "OVERDUE" | PAYOUT_FAILED | Payout failed due to a fulfillment issue and is currently being retried. | + | 5 | impactConnection is connected AND publisher.withdrawalSettings is missing | PENDING | Complete your tax and cash payout setup to receive your rewards. | + | 6 | reward.pendingReasons includes "MISSING_PAYOUT_CONFIGURATION" AND publisher.withdrawalSettings is missing | PENDING | Complete your tax and cash payout setup to receive your rewards. | + | 7 | partnerFundsTransfer.dateScheduled is in the future | 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 and scheduled for payment based on your settings. | + | 9 | reward.dateCancelled is set | CANCELLED | Jan 1, 2026 | + | 10 | reward.statuses includes "EXPIRED" | EXPIRED | Jan 1, 2026 | + | 11 | reward.statuses includes "PENDING" | PENDING | Until Jan 1, 2026 | + | 12 | reward.type is "CREDIT" AND statuses includes "REDEEMED" | REDEEMED | Jan 1, 2026 | + | 13 | reward.type is "CREDIT" | AVAILABLE | Expires Jan 1, 2026 | + | 14 | reward.type is "PCT_DISCOUNT" AND statuses includes "AVAILABLE" | AVAILABLE | Expires Jan 1, 2026 | + | 15 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "PENDING" | PENDING | Until Jan 1, 2026 | + | 16 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "CANCELLED" | CANCELLED | Jan 1, 2026 | + | 17 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "AVAILABLE" | AVAILABLE | Expires Jan 1, 2026 | + | 18 | none of the above | "" (no badge text) | (no description) | + # ============================================================ + # 3. STATUS → BADGE COLOUR MAPPING + # ============================================================ + + @motivating @ui + Scenario Outline: Each status maps to a badge colour and default text + Given a reward whose computed status is + When the cell is rendered + Then the badge is shown in a pill with + + Examples: + | status | text | pillColour | + | AVAILABLE | Available | success | + | REDEEMED | Redeemed | primary | + | PAYOUT_APPROVED | Payout Approved | primary | + | PENDING | Pending | warning | + | PENDING_REVIEW | Pending | warning | + | PROCESSING | Processing | warning | + | CANCELLED | Cancelled | danger | + | EXPIRED | Expired | danger | + | DENIED | Denied | danger | + | PAYOUT_FAILED | Payout Failed | danger | + | PAYOUT_CANCELLED | Payout Cancelled | danger | + # ============================================================ + # 4. DESCRIPTION LINE PRECEDENCE + # The description under the badge is selected in this order: + # 1. Fraud descriptions (PENDING_REVIEW / DENIED) + # 2. Pending-reason descriptions (only when status is PENDING) + # 3. Payout descriptions (PAYOUT_APPROVED / PAYOUT_FAILED / + # PAYOUT_CANCELLED / PROCESSING) + # 4. Reward date (dateCancelled / dateExpires / dateRedeemed), + # prefixed with the configured expiry-text when status is AVAILABLE + # ============================================================ + + @motivating @ui + Scenario Outline: Fraud descriptions are shown for fraud-derived statuses + Given a reward whose computed status is + When the cell is rendered + Then the description line shows + + Examples: + | status | description | + | DENIED | Detected self-referral | + | PENDING_REVIEW | Awaiting review | + + @motivating @ui + Scenario Outline: Date-based descriptions for terminal lifecycle states + Given a reward whose computed status is + And the reward has populated + When the cell is rendered + Then the description line shows + + Examples: + | status | date | text | + | AVAILABLE | dateExpires | Expires | + | EXPIRED | dateExpires | | + | CANCELLED | dateCancelled | | + | REDEEMED | dateRedeemed | | + + @motivating @ui + Scenario Outline: Payout descriptions for PFT-derived statuses + Given a reward whose computed status is + When the cell is rendered + Then the description line shows + + Examples: + | status | description | + | PAYOUT_APPROVED | Payout approved and scheduled for payment 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 . Payout is then scheduled based on your settings. | + # ============================================================ + # 5. PENDING DESCRIPTION RESOLUTION (getTaxPendingReasons) + # When the computed status is PENDING the description is the first + # non-empty value of: + # a) tax/payout reason (getTaxPendingReasons) + # b) reward.pendingReasons mapped through pendingCodeMap, joined by ", " + # ============================================================ + + @motivating @ui + Scenario Outline: US_TAX pending reason resolves based on the tax connection state + Given the user has a PENDING reward whose pendingReasons include "US_TAX" + And the user's impactConnection.taxHandlingEnabled is + And the user's impactConnection.connected is + And the user's publisher.requiredTaxDocumentType is + And the user's publisher.currentTaxDocument is + And the user's publisher.currentTaxDocument.status is + And the user's publisher.withdrawalSettings is + Then the description line shows + + Examples: + | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | description | + | false | true | W9 | present | ACTIVE | present | W-9 required | + | true | false | - | - | - | - | Complete your tax and cash payout setup to receive your rewards. | + | true | true | W9 | missing | - | present | Submit your tax documents to receive your rewards. | + | true | true | W9 | present | INACTIVE | present | Invalid tax form. Submit a new form to receive your rewards. | + | true | true | W9 | present | NOT_VERIFIED | present | Awaiting tax form review. | + | true | true | W9 | present | ACTIVE | missing | Complete your tax and cash payout setup to receive your rewards. | + + @motivating @ui + Scenario: MISSING_PAYOUT_CONFIGURATION pending reason shows the partner-creation prompt + Given the user has a PENDING reward whose pendingReasons include "MISSING_PAYOUT_CONFIGURATION" + And the user is connected with publisher.withdrawalSettings missing + Then the description line shows "Complete your tax and cash payout setup to receive your rewards." + + @motivating @ui + Scenario: Connected user without withdrawal settings shows the partner-creation prompt as a fallback + Given the user has a PENDING reward whose pendingReasons array is empty + And the user is connected with publisher.withdrawalSettings missing + Then the description line shows "Complete your tax and cash payout setup to receive your rewards." + + @motivating @ui + Scenario Outline: Generic pendingReasons codes are mapped via pendingCodeMap + Given the user has a PENDING reward + And the reward has no tax/payout pending reason + And the reward.pendingReasons include + When the cell is rendered + Then the description line shows + + Examples: + | pendingReason | text | + | SCHEDULED | Until | + | UNHANDLED_ERROR | Fulfillment error | + | SUSPECTED_FRAUD | Awaiting review | + + @minutia + Scenario: Multiple pending reasons are joined with commas + Given the user has a PENDING reward + And the reward has no tax/payout pending reason + And the reward.pendingReasons include both "UNHANDLED_ERROR" and "SCHEDULED" + Then the description line shows the mapped strings joined by ", " + + @minutia + Scenario: Unknown pending reason codes pass through verbatim + Given the user has a PENDING reward + And the reward.pendingReasons include a code not present in the pendingCodeMap + Then the raw code is shown in the description line + # ============================================================ + # 6. STATUS TEXT CUSTOMIZATION (status-text prop) + # ============================================================ + + @motivating + Scenario Outline: Statuses can be customized via ICU format + Given the "status-text" prop is "{status, select, AVAILABLE {Redeem me!} CANCELLED {Unavailable} PENDING {Coming soon!} EXPIRED {Past due} REDEEMED {Spent} PENDING_REVIEW {Pending Review!} PAYOUT_APPROVED {Payout Sent!} PAYOUT_FAILED {Payout Failed!} PAYOUT_CANCELLED {Payout Cancelled!} PROCESSING {Processing!} DENIED {Unlucky!}}" + And a reward whose computed status is + When the cell is rendered + Then the badge is rendered in a pill with text + + Examples: + | status | text | pillColour | + | AVAILABLE | Redeem me! | success | + | CANCELLED | Unavailable | danger | + | PENDING | Coming soon! | warning | + | EXPIRED | Past due | danger | + | REDEEMED | Spent | primary | + | PENDING_REVIEW | Pending Review! | warning | + | PAYOUT_APPROVED | Payout Sent! | primary | + | PAYOUT_FAILED | Payout Failed! | danger | + | PAYOUT_CANCELLED | Payout Cancelled! | danger | + | PROCESSING | Processing! | warning | + | DENIED | Unlucky! | danger | + # ============================================================ + # 7. LOCALIZATION + # ============================================================ + + @minutia + Scenario: Dates are formatted in the user's locale + Given the "locale" prop is set to a supported locale + Then every placeholder above is formatted with that locale + And the configurable text props (expiryText, payoutProcessing, etc.) flow through `intl.formatMessage` 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..8509e4bae9 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 @@ -58,21 +58,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 +128,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-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index d1068c7ba1..f28bfb36e6 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -288,6 +288,7 @@ graph TD; sqm-widget-verification --> sqm-partner-info-modal sqm-widget-verification --> sqm-code-verification sqm-widget-verification --> sqm-email-verification + sqm-partner-info-modal --> sqm-form-message style sqm-stencilbook fill:#f9f,stroke:#333,stroke-width:4px ``` 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 68f4733144..f3fd7c6281 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,198 @@ ## 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} }"` | -| `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}}"` | -| `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}} 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} }"` | +| `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_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_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}}"` | +| `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}} 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"` | ## 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 785612c682..4d4c3cdb84 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 @@ -1385,7 +1379,7 @@ export class TaxAndCashMonolith { {this.supportLink} ), - } + }, ) as string } /> @@ -1408,7 +1402,7 @@ function useDemoTaxAndCash(props: TaxAndCashMonolith) { key === "sqm-tax-and-cash" ? { ...prev, ...states[key] } : { ...prev, [`${key}_stateController`]: states[key] }, - {} + {}, ); return deepmerge( @@ -1417,6 +1411,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-user-info-form/UserInfoFormView.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx index 67fcb15ea2..467e7bb825 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 @@ -79,7 +79,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", 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/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 7bd16b48f5..a5618bff0a 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 @@ -93,7 +93,6 @@ export interface UserInfoFormViewProps { region: string; postalCode: string; currency: string; - currencyHelpText: string; allowBankingCollection: string; personalInformation: string; continueButton: string; @@ -822,7 +821,6 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { menu value={formState.currency} disabled={states.disabled || isDisabledPartnerInput("currency")} - helpText={text.currencyHelpText} {...(formState.errors?.currency ? { class: classes.ErrorInput, diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx index feed781945..b7136c3561 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.tsx @@ -66,11 +66,6 @@ export class TaxForm { * @uiName Currency field label */ @Prop() currency: string = "Currency"; - - /** - * @uiName Currency field help text - */ - @Prop() currencyHelpText: string = "Choose your preferred payout currency"; /** * @uiName Setup progress */ @@ -275,6 +270,6 @@ function useDemoUserInfoForm(props: TaxForm): UseUserInfoFormResult { }, }, props.demoData || {}, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } From 7a9aa6b405af88c754abfa1f8e8e9a3f03cde398 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 8 May 2026 08:38:50 -0700 Subject: [PATCH 28/64] Add new gql field for rewards rewardedCash. Update reward state logic in sqm-rewards-table-status-cell and sqm-referral-table-rewards-column. Added new spec sheets to cover logic of functions --- .../ReferralTable.stories.tsx | 1 + .../ReferralTableRewardsCell.stories.tsx | 1 + ...ndCashReferralTableRewardsCell.stories.tsx | 543 +++++++----- .../cells/sqm-referral-table-rewards-cell.tsx | 32 +- .../referral-table-rewards-column-new.feature | 780 ++++++++++++++++++ .../RewardsTableCell.stories.tsx | 1 + .../TaxAndCashRewardsTableCell.stories.tsx | 660 ++++++++------- .../sqm-rewards-table/cells/readme.md | 2 +- .../cells/sqm-rewards-table-status-cell.tsx | 39 +- ...qm-rewards-table-status-column-new.feature | 166 ++-- .../sqm-rewards-table/useRewardsTable.tsx | 1 + packages/mint-components/src/saasquatch.d.ts | 1 + 12 files changed, 1595 insertions(+), 632 deletions(-) create mode 100644 packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature 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/TaxAndCashReferralTableRewardsCell.stories.tsx b/packages/mint-components/src/components/sqm-referral-table/TaxAndCashReferralTableRewardsCell.stories.tsx index 1482d36a80..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,270 +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 ( - ( + - ); -}; + }, + ]} + taxConnection={fullySetupTaxConnection} + {...defaultProps} + > +); -export const PartnerNotCreatedSetupNotStarted = () => { - return ( - ( + - ); -}; + }, + ]} + taxConnection={fullySetupTaxConnection} + {...defaultProps} + > +); -export const PartnerCreatedSetupStartedButIncomplete = () => { - 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 a0b3783272..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"; } @@ -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-new.feature b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature new file mode 100644 index 0000000000..8505ece74f --- /dev/null +++ b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature @@ -0,0 +1,780 @@ +@author:derek @owner:derek +Feature: Referral Table Rewards Cell + Renders the rewards earned for each referral as an `` block. + Each reward shows: + - the reward value (e.g. "$50.00") in the summary + - a coloured badge "pill" with status text + - optionally a second "info" pill (only for AVAILABLE rewards with an expiry) + - a description body that is a STACK of zero or more conditional lines: + 1. a state-specific status line (only for some states) + 2. a "reward received" line (only when reward.dateGiven is set) + 3. a fuel-tank code line (only when reward.fuelTankCode is set) + The state shown is computed by `getState()` from: + 1. the referral's fraud moderation status, + 2. the user's Impact tax/payout connection (taxConnection prop), + 3. the reward's PartnerFundsTransfer (PFT), + 4. the reward's `pendingReasons` (US_TAX, MISSING_PAYOUT_CONFIGURATION), + 5. the reward's `statuses` array (fallback). + + Background: + Given the user is viewing the referral table + And each row may show one or more rewards via sqm-referral-table-rewards-cell + And the reward value is always shown in the summary as the bold prettyValue (e.g. "$50.00") + # ============================================================ + # 1. CELL CONFIGURATION (every prop in isolation) + # ============================================================ + + @motivating @ui + Scenario: hideDetails=true hides the disclosure caret and disables expansion + Given the "hide-details" prop is true + When the cell is rendered + Then the sl-details summary-icon is set to display:none + And the sl-details element is rendered with the disabled attribute + And the summary cursor style is "default" + + @motivating @ui + Scenario: hideDetails=false (default) shows the disclosure caret and allows expansion + Given the "hide-details" prop is false + When the cell is rendered + Then the sl-details summary-icon is set to display:flex + And the summary cursor style is "pointer" + + @minutia @ui + Scenario Outline: rewardReceivedText prop drives the reward-received line + Given a reward with dateGiven Jan 1, 2026 + And the "reward-received-text" prop is "" + When the cell is rendered + Then the description body contains " Jan 1, 2026" with "Jan 1, 2026" rendered in bold + + Examples: + | value | + | Reward given | + | You earned on | + | (empty string) | + + @minutia @ui + Scenario Outline: expiringText prop drives the AVAILABLE info pill + Given an AVAILABLE reward with dateExpires Aug 31, 2026 + And the current date is May 7, 2026 + And the "expiring-text" prop is "" + When the cell is rendered + Then the second info pill text is exactly "" + + Examples: + | value | rendered | + | Expiring | Expiring 3 months | + | Expires | Expires 3 months | + | (unset) | undefined 3 months | + + @minutia @ui + Scenario Outline: pendingForText prop drives the badge text for PENDING+dateScheduledFor + Given a PENDING reward with dateScheduledFor Aug 31, 2026 + And the current date is May 7, 2026 + And the "pending-for-text" prop is "" + When the cell is rendered + Then the primary badge text is exactly "" + + Examples: + | value | rendered | + | {status} for {date} | Pending for 3 months | + | Available {date} | Available 3 months | + + @motivating @ui + Scenario: deniedHelpText prop is appended to the DENIED description with a leading space + Given a DENIED reward with referral.dateModerated Jan 1, 2026 + And the "denied-help-text" prop is "Contact support if you believe this is an error." + When the cell is rendered + Then the description body contains "Denied on Jan 1, 2026. Contact support if you believe this is an error." + + @motivating @ui + Scenario: deniedHelpText prop omitted leaves the DENIED description with only the trailing period + Given a DENIED reward with referral.dateModerated Jan 1, 2026 + And the "denied-help-text" prop is unset + When the cell is rendered + Then the description body contains "Denied on Jan 1, 2026." + And the description body does NOT contain any text after that period + + @motivating @ui + Scenario: fuelTankText prop drives the fuel-tank line + Given a reward with fuelTankCode "ABC-123" + And the "fuel-tank-text" prop is "Your code:" + When the cell is rendered + Then the description body contains "Your code: ABC-123" with "ABC-123" rendered in bold + + @minutia @ui + Scenario: locale prop changes the date formatter and relative time formatter + Given the "locale" prop is set to a supported locale + Then every Luxon date is formatted using `luxonLocale(locale)` with `DateTime.DATE_MED` + And every relative time uses `DateTime.toRelative()` with the same locale, with the leading "in " stripped + And every prop-driven message string is run through `intl.formatMessage` + # ============================================================ + # 2. STATE PRECEDENCE LADDER + # `getState()` walks the rules below from top to bottom and returns + # at the FIRST matching rule. Note: rules 3a and 3b (cash-reward payout- + # setup gate) fire BEFORE any PFT branch, so they suppress PFT-derived + # PROCESSING / PAYOUT_APPROVED states whenever the user has a cash + # reward but has not finished tax/payout setup (either not connected at + # all, or connected without withdrawalSettings). For cash rewards, + # PAYOUT_CANCELLED (REVERSED) and PAYOUT_FAILED (OVERDUE) are + # unreachable past rules 3a/3b when those gates fire. + # ============================================================ + + @motivating + Scenario: State precedence ladder + Given a reward, its referral's fraud state, and the user's tax connection + And dummy data is used where applicable: dateModerated/dateExpires/dateCancelled/dateScheduledFor/partnerFundsTransfer.dateScheduled = Jan 1, 2026; deniedHelpText = "Contact support." + Then the displayed state is determined in the following order, returning at the first match: + | order | rule | resulting state | resulting badge text | resulting description body | + | 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 | + | 19 | none of the above | undefined | Not available | (no state-specific body line) | + But for rewards where reward.rewardedCash is false (non-cash rewards, e.g. CREDIT/POINTS, PCT_DISCOUNT, INTEGRATION, FUELTANK — these skip rules 3a/3b entirely and never carry a partnerFundsTransfer or US_TAX/MISSING_PAYOUT_CONFIGURATION pendingReason, so they always fall through to the reward.statuses fallback), the following non-cash-only rules are inserted between rule 14 and rule 18, in this order: + | order | rule | resulting state | resulting badge text | resulting description body | + | 15 | statuses includes "REDEEMED" | REDEEMED | Redeemed | (no state-specific body line) | + | 16 | statuses includes "CANCELLED" | CANCELLED | Cancelled | Reward cancelled on Jan 1, 2026 | + | 17 | statuses includes "EXPIRED" | EXPIRED | Expired | Reward expired on Jan 1, 2026 | + | 17a | 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) | + | 17b | statuses includes "AVAILABLE" | AVAILABLE | Available (plus info pill "{expiringText} {relative}" if dateExpires set) | Reward expiring on Jan 1, 2026 | + + @motivating + Scenario: Cash reward with connected partner but no withdrawal settings overrides any PFT-derived state + Given a cash reward (reward.rewardedCash is true) with partnerFundsTransfer status "TRANSFERRED" and dateScheduled in the past + And taxConnection.connected is true + And taxConnection.publisher.withdrawalSettings is missing + When the cell is rendered + Then the resulting state is PENDING_PARTNER_CREATION + + @motivating + Scenario: Cash reward with no impact connection overrides any PFT-derived state + Given a cash reward (reward.rewardedCash is true) with partnerFundsTransfer status "TRANSFERRED" and dateScheduled in the past + And taxConnection.connected is false + When the cell is rendered + Then the resulting state is PENDING_PARTNER_CREATION + + @motivating + Scenario: Non-cash reward skips the payout-setup gate + Given a non-cash reward (reward.rewardedCash is false) with reward.statuses ["AVAILABLE"] + And taxConnection.connected is false + When the cell is rendered + Then the resulting state is AVAILABLE + + @minutia + Scenario: Connected partner with no withdrawal settings does NOT override fraud states + Given a reward whose referral.fraudData.moderationStatus is "DENIED" + And taxConnection.connected is true + And taxConnection.publisher.withdrawalSettings is missing + When the cell is rendered + Then the resulting state is DENIED + # ============================================================ + # 3. STATE → BADGE COLOUR (`getSLBadgeType`) + # ============================================================ + + @motivating @ui + Scenario Outline: Each state maps to a Shoelace badge type + Given a reward whose computed state is "" + When the cell is rendered + Then the primary pill is rendered with type "" + And the primary pill is rendered with the "" CSS class + # CSS class applied: primary→RedeemBadge, danger→DangerBadge, + # warning→WarningBadge, success→SuccessBadge, info→WarningBadge. + # Note: getSLBadgeType returns undefined for any state outside the cases + # below; the resulting CSS class then defaults to WarningBadge. + + Examples: + | state | slBadgeType | cssClass | + | REDEEMED | primary | RedeemBadge | + | PAYOUT_APPROVED | primary | RedeemBadge | + | DENIED | danger | DangerBadge | + | EXPIRED | danger | DangerBadge | + | CANCELLED | danger | DangerBadge | + | PAYOUT_FAILED | danger | DangerBadge | + | PAYOUT_CANCELLED | danger | DangerBadge | + | PENDING | warning | WarningBadge | + | PENDING_REVIEW | warning | WarningBadge | + | PENDING_TAX_REVIEW | warning | WarningBadge | + | PENDING_NEW_TAX_FORM | warning | WarningBadge | + | PENDING_TAX_SUBMISSION | warning | WarningBadge | + | PENDING_PARTNER_CREATION | warning | WarningBadge | + | PROCESSING | warning | WarningBadge | + | AVAILABLE | success | SuccessBadge | + | (empty string "") | undefined | WarningBadge | + | (any unknown string) | undefined | WarningBadge | + # ============================================================ + # 4. PRIMARY BADGE TEXT + # The primary badge ALWAYS renders. Its text comes from one of two paths: + # - "pendingFor" overlay: state==="PENDING" AND reward.dateScheduledFor truthy + # - default: intl.formatMessage(statusText, {status: state}) + # The default `statusText` ICU message maps every state below. + # ============================================================ + + @motivating @ui + Scenario Outline: Default primary badge text per state (no PENDING+dateScheduledFor overlay) + Given a reward whose computed state is "" + And NOT (state is PENDING and reward.dateScheduledFor is set) + And the "status-text" prop is the default + When the cell is rendered + Then the primary badge text is exactly "" + + Examples: + | state | text | + | 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 | + | (empty string "") | Not available | + | (any unknown string) | Not available | + + @motivating @ui + Scenario: PENDING + dateScheduledFor uses the pendingForText overlay (default ICU) + Given a PENDING reward with dateScheduledFor Aug 31, 2026 + And the current date is May 7, 2026 + And the "status-text" prop is the default + And the "pending-for-text" prop is the default "{status} for {date}" + When the cell is rendered + Then the primary badge text is exactly "Pending for 3 months" + + @motivating @ui + Scenario: PENDING WITHOUT dateScheduledFor falls back to the default badge text + Given a PENDING reward whose dateScheduledFor is unset + When the cell is rendered + Then the primary badge text is exactly "Pending" + + @minutia @ui + Scenario: pendingForText overlay is suppressed for non-PENDING states even with dateScheduledFor + Given an AVAILABLE reward whose dateScheduledFor is set Aug 31, 2026 + When the cell is rendered + Then the primary badge text is exactly "Available" + And the pendingForText template is NOT applied + # ============================================================ + # 5. SECONDARY ("INFO") PILL + # The second pill renders ONLY when state==="AVAILABLE" AND reward.dateExpires is truthy. + # Its text is `${expiringText} ${getTimeDiff(dateExpires)}` — note the leading space + # always present and the prop value is rendered as-is (undefined → literal "undefined ..."). + # ============================================================ + + @motivating @ui + Scenario Outline: Secondary info pill renders only for AVAILABLE+dateExpires + Given a reward whose computed state is "" + And reward.dateExpires is + When the cell is rendered + Then the secondary info pill is rendered: + + Examples: + | state | dateExpires | rendered | + | AVAILABLE | Aug 31, 2026 (set) | yes | + | AVAILABLE | unset | no | + | EXPIRED | Aug 31, 2026 (set) | no | + | REDEEMED | Aug 31, 2026 (set) | no | + | PENDING | Aug 31, 2026 (set) | no | + | CANCELLED | Aug 31, 2026 (set) | no | + | PENDING_REVIEW | Aug 31, 2026 (set) | no | + | DENIED | Aug 31, 2026 (set) | no | + | PAYOUT_APPROVED | Aug 31, 2026 (set) | no | + | PROCESSING | Aug 31, 2026 (set) | no | + + @motivating @ui + Scenario Outline: Secondary info pill text composition + Given an AVAILABLE reward with dateExpires Aug 31, 2026 + And the current date is May 7, 2026 + And the "expiring-text" prop is + When the cell is rendered + Then the secondary info pill is rendered with type "info" + And the secondary info pill is rendered with the WarningBadge CSS class + And the secondary info pill text is exactly "" + + Examples: + | expiringText | text | + | "Expiring" | Expiring 3 months | + | "" | 3 months | + | unset | undefined 3 months | + # ============================================================ + # 6. DESCRIPTION BODY — PER-STATE STATUS LINE + # The body is a stack of conditional render blocks, evaluated in source order: + # PENDING_REVIEW → "" → PROCESSING → PAYOUT_FAILED → PAYOUT_CANCELLED → + # PENDING_TAX_REVIEW → PENDING_NEW_TAX_FORM → PENDING_TAX_SUBMISSION → + # PENDING_PARTNER_CREATION → DENIED → dateGiven → EXPIRED → CANCELLED → + # PENDING → AVAILABLE → fuelTankCode. + # All other states (REDEEMED, etc.) render NO state-specific + # status line — only the dateGiven/fuelTankCode lines below if applicable. + # ============================================================ + + @motivating @ui + Scenario: PENDING_REVIEW with dateModerated renders the "Pending since" line + Given a reward whose computed state is PENDING_REVIEW + And reward.referral.dateModerated is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Pending since Jan 1, 2026" with "Jan 1, 2026" in bold + + @motivating @ui + Scenario: PENDING_REVIEW WITHOUT dateModerated renders no "Pending since" line + Given a reward whose computed state is PENDING_REVIEW + And reward.referral.dateModerated is unset + When the cell is rendered + Then the description body does NOT contain a "Pending since" line + + @motivating @ui + Scenario: state === "" renders the bare "Not available" statusText line + Given a reward whose computed state is the empty string "" + When the cell is rendered + Then the description body contains exactly the text "Not available" + + @minutia @ui + Scenario: state === undefined does NOT render the "Not available" line in the body + Given a reward whose computed state is undefined + When the cell is rendered + Then the description body does NOT contain "Not available" + And only the dateGiven and fuelTankCode lines (if any) are rendered + + @motivating @ui + Scenario: PROCESSING renders the "Processing until {date}." line + Given a reward whose computed state is PROCESSING + And reward.partnerFundsTransfer.dateScheduled is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Processing until Jan 1, 2026. Payout is then scheduled based on your settings." + + @motivating @ui + Scenario: PAYOUT_APPROVED renders the "Processing until {date}." line + Given a reward whose computed state is PAYOUT_APPROVED + And reward.partnerFundsTransfer.dateScheduled is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Processing until Jan 1, 2026. Payout is then scheduled based on your settings." + + @motivating @ui + Scenario: PAYOUT_FAILED renders the failed-fulfillment line + Given a reward whose computed state is PAYOUT_FAILED + When the cell is rendered + Then the description body contains "Payout failed due to a fulfillment issue and is currently being retried." + + @motivating @ui + Scenario: PAYOUT_CANCELLED renders the contact-support line + Given a reward whose computed state is PAYOUT_CANCELLED + When the cell is rendered + Then the description body contains "If you think this is a mistake, contact our Support team." + + @motivating @ui + Scenario: PENDING_TAX_REVIEW renders the tax-review line + Given a reward whose computed state is PENDING_TAX_REVIEW + When the cell is rendered + Then the description body contains "Awaiting tax form review" + + @motivating @ui + Scenario: PENDING_NEW_TAX_FORM renders the invalid-tax-form line + Given a reward whose computed state is PENDING_NEW_TAX_FORM + When the cell is rendered + Then the description body contains "Invalid tax form. Submit a new form to receive your rewards." + + @motivating @ui + Scenario: PENDING_TAX_SUBMISSION renders the submit-tax-documents line + Given a reward whose computed state is PENDING_TAX_SUBMISSION + When the cell is rendered + Then the description body contains "Submit your tax documents to receive your rewards" + + @motivating @ui + Scenario: PENDING_PARTNER_CREATION renders the complete-payout-setup line + Given a reward whose computed state is PENDING_PARTNER_CREATION + When the cell is rendered + Then the description body contains "Complete your tax and cash payout setup to receive your rewards" + + @motivating @ui + Scenario: DENIED with dateModerated renders "Denied on {date}." with trailing period + Given a reward whose computed state is DENIED + And reward.referral.dateModerated is Jan 1, 2026 + And the "denied-help-text" prop is unset + When the cell is rendered + Then the description body contains "Denied on Jan 1, 2026." with "Jan 1, 2026" in bold + + @motivating @ui + Scenario: DENIED with dateModerated AND deniedHelpText appends help text after the period + Given a reward whose computed state is DENIED + And reward.referral.dateModerated is Jan 1, 2026 + And the "denied-help-text" prop is "Contact our support team." + When the cell is rendered + Then the description body contains "Denied on Jan 1, 2026. Contact our support team." + + @motivating @ui + Scenario: DENIED WITHOUT dateModerated renders no "Denied on" line + Given a reward whose computed state is DENIED + And reward.referral.dateModerated is unset + When the cell is rendered + Then the description body does NOT contain a "Denied on" line + And the deniedHelpText is NOT rendered (it lives inside the dateModerated block) + + @motivating @ui + Scenario: EXPIRED with dateExpires renders the "Reward expired on {date}" line + Given a reward whose computed state is EXPIRED + And reward.dateExpires is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Reward expired on Jan 1, 2026" with "Jan 1, 2026" in bold + + @motivating @ui + Scenario: EXPIRED WITHOUT dateExpires renders no expired line + Given a reward whose computed state is EXPIRED + And reward.dateExpires is unset + When the cell is rendered + Then the description body does NOT contain "Reward expired on" + + @motivating @ui + Scenario: CANCELLED with dateCancelled renders the "Reward cancelled on {date}" line + Given a reward whose computed state is CANCELLED + And reward.dateCancelled is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Reward cancelled on Jan 1, 2026" with "Jan 1, 2026" in bold + + @motivating @ui + Scenario: CANCELLED WITHOUT dateCancelled renders no cancelled line + Given a reward whose computed state is CANCELLED + And reward.dateCancelled is unset + When the cell is rendered + Then the description body does NOT contain "Reward cancelled on" + + @motivating @ui + Scenario: PENDING with dateScheduledFor renders the "Available on {date}" line + Given a reward whose computed state is PENDING + And reward.dateScheduledFor is Jan 1, 2026 + When the cell is rendered + Then the description body contains "Available on Jan 1, 2026" with "Jan 1, 2026" in bold + + @motivating @ui + Scenario: PENDING WITHOUT dateScheduledFor renders no "Available on" line + Given a reward whose computed state is PENDING + And reward.dateScheduledFor is unset + When the cell is rendered + Then the description body does NOT contain "Available on" + And the badge falls back to plain "Pending" + + @motivating @ui + Scenario: AVAILABLE with dateExpires renders the "Reward expiring on {date}" body line + Given a reward whose computed state is AVAILABLE + And reward.dateExpires is Aug 31, 2026 + When the cell is rendered + Then the description body contains "Reward expiring on Aug 31, 2026" with "Aug 31, 2026" in bold + + @motivating @ui + Scenario: AVAILABLE WITHOUT dateExpires renders no body line and no info pill + Given a reward whose computed state is AVAILABLE + And reward.dateExpires is unset + When the cell is rendered + Then the description body does NOT contain "Reward expiring on" + And the secondary info pill is NOT rendered + + @motivating @ui + Scenario: REDEEMED renders no state-specific body line + Given a reward whose computed state is REDEEMED + When the cell is rendered + Then the description body has NO state-specific status line + And only the dateGiven and fuelTankCode lines (if any) are rendered + # ============================================================ + # 7. ALWAYS-ON BODY LINES (dateGiven + fuelTankCode) + # These render REGARDLESS of state, layered on top of (or instead of) + # the per-state status line. + # ============================================================ + + @motivating @ui + Scenario Outline: dateGiven line renders for every state when reward.dateGiven is truthy + Given a reward with dateGiven Jan 1, 2026 + And the "reward-received-text" prop is "Reward given" + And the reward's computed state is "" + When the cell is rendered + Then the description body contains "Reward given Jan 1, 2026" with "Jan 1, 2026" in bold + + Examples: + | state | + | AVAILABLE | + | REDEEMED | + | PENDING | + | PENDING_REVIEW | + | EXPIRED | + | CANCELLED | + | DENIED | + | PAYOUT_APPROVED | + | PAYOUT_CANCELLED | + | PAYOUT_FAILED | + | PROCESSING | + | PENDING_TAX_REVIEW | + | PENDING_NEW_TAX_FORM | + | PENDING_TAX_SUBMISSION | + | PENDING_PARTNER_CREATION | + + @motivating @ui + Scenario: dateGiven line is suppressed when reward.dateGiven is unset + Given a reward whose dateGiven is unset + When the cell is rendered + Then the description body does NOT contain a reward-received line + + @motivating @ui + Scenario Outline: fuelTankCode line renders for every state when reward.fuelTankCode is truthy + Given a reward with fuelTankCode "ABC-123" + And the "fuel-tank-text" prop is "Code:" + And the reward's computed state is "" + When the cell is rendered + Then the description body contains "Code: ABC-123" with "ABC-123" in bold + + Examples: + | state | + | AVAILABLE | + | REDEEMED | + | PENDING | + | PENDING_REVIEW | + | EXPIRED | + | CANCELLED | + | DENIED | + | PAYOUT_APPROVED | + | PAYOUT_CANCELLED | + | PAYOUT_FAILED | + | PROCESSING | + | PENDING_TAX_REVIEW | + | PENDING_NEW_TAX_FORM | + | PENDING_TAX_SUBMISSION | + | PENDING_PARTNER_CREATION | + + @motivating @ui + Scenario: fuelTankCode line is suppressed when reward.fuelTankCode is unset + Given a reward whose fuelTankCode is unset + When the cell is rendered + Then the description body does NOT contain a fuel-tank line + + @minutia @ui + Scenario: Stacking order in the description body + Given an AVAILABLE reward with dateExpires Aug 31, 2026, dateGiven Jan 1, 2026, fuelTankCode "ABC-123" + When the cell is rendered + Then the description body lines appear in this order: + | order | line | + | 1 | Reward given Jan 1, 2026 (dateGiven block) | + | 2 | Reward expiring on Aug 31, 2026 (AVAILABLE block) | + | 3 | ABC-123 (fuelTankCode block) | + # ============================================================ + # 8. PARTNER FUNDS TRANSFER (PFT) MATRIX + # Every PFT permutation, with the cash-reward "needsPayoutSetup" gate + # (rules 3a/3b above) toggled on/off. The gate fires BEFORE PFT branches + # for cash rewards, but rules 4–5 (REVERSED, OVERDUE) are unreachable + # when the gate fires because the gate already returned + # PENDING_PARTNER_CREATION. + # ============================================================ + + @motivating + Scenario Outline: PFT permutations when withdrawalSettings ARE present (gate does not fire) + Given taxConnection.connected is true + And taxConnection.publisher.withdrawalSettings is present + And the reward has a partnerFundsTransfer with status and dateScheduled + When the cell is rendered + Then the resulting state is "" + + Examples: + | pftStatus | pftDateScheduled | state | + | REVERSED | (any) | PAYOUT_CANCELLED | + | OVERDUE | (any) | PAYOUT_FAILED | + | TRANSFERRED | future | PROCESSING | + | TRANSFERRED | past | PAYOUT_APPROVED | + | TRANSFERRED | unset | PAYOUT_APPROVED | + | NOT_YET_DUE | future | PROCESSING | + | NOT_YET_DUE | past | PAYOUT_APPROVED | + | NOT_YET_DUE | unset | PAYOUT_APPROVED | + | (null) | future | PROCESSING | + | (null) | past | PAYOUT_APPROVED | + + @motivating + Scenario Outline: PFT permutations when withdrawalSettings are MISSING (gate fires first) + Given taxConnection.connected is true + And taxConnection.publisher.withdrawalSettings is missing + And the reward has a partnerFundsTransfer with status and dateScheduled + When the cell is rendered + Then the resulting state is "" + + Examples: + | pftStatus | pftDateScheduled | state | + | REVERSED | (any) | PENDING_PARTNER_CREATION | + | OVERDUE | (any) | PENDING_PARTNER_CREATION | + | TRANSFERRED | future | PENDING_PARTNER_CREATION | + | TRANSFERRED | past | PENDING_PARTNER_CREATION | + | NOT_YET_DUE | future | PENDING_PARTNER_CREATION | + | NOT_YET_DUE | past | PENDING_PARTNER_CREATION | + | (null) | future | PENDING_PARTNER_CREATION | + | (null) | past | PENDING_PARTNER_CREATION | + + @motivating + Scenario: PFT branch is skipped entirely when reward.partnerFundsTransfer is missing + Given the reward has no partnerFundsTransfer + And the reward has no fraud / pendingReasons / statuses overrides + When the cell is rendered + Then PFT rules 4–9 are skipped + And the resulting state is determined by the next applicable rule + # ============================================================ + # 9. US_TAX PENDING-REASON MATRIX + # Every permutation of taxConnection state when reward.pendingReasons + # includes "US_TAX". Once US_TAX matches a sub-rule, no later rule fires. + # ============================================================ + + @motivating @ui + Scenario Outline: US_TAX pending matrix + Given a reward whose pendingReasons include "US_TAX" + And taxConnection.taxHandlingEnabled is + And taxConnection.connected is + And taxConnection.publisher.requiredTaxDocumentType is + And taxConnection.publisher.currentTaxDocument is + And taxConnection.publisher.currentTaxDocument.status is + And taxConnection.publisher.withdrawalSettings is + When the cell is rendered + Then the resulting state is "" + + Examples: + | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | state | + | false | (any) | (any) | (any) | (any) | (any) | PENDING | + | true | false | (any) | (any) | (any) | (any) | PENDING_PARTNER_CREATION | + | true | true | unset | (any) | (any) | present | (falls through) | + | true | true | unset | (any) | (any) | missing | (falls through) | + | true | true | W9 | missing | - | (any) | PENDING_TAX_SUBMISSION | + | true | true | W9 | present | INACTIVE | (any) | PENDING_NEW_TAX_FORM | + | true | true | W9 | present | INVALID_W9_ELECTRONIC_DOCUMENT | (any) | PENDING_NEW_TAX_FORM | + | true | true | W9 | present | INVALID_W9_ELECTRONIC_DOCUMENT_CHECK_INTERNAL | (any) | PENDING_NEW_TAX_FORM | + | true | true | W9 | present | NOT_VERIFIED | (any) | PENDING_TAX_REVIEW | + | true | true | W9 | present | ACTIVE | missing | PENDING_PARTNER_CREATION | + | true | true | W9 | present | ACTIVE | present | (falls through) | + + @motivating @ui + Scenario: MISSING_PAYOUT_CONFIGURATION pendingReason resolves to PENDING_PARTNER_CREATION + Given a reward whose pendingReasons include "MISSING_PAYOUT_CONFIGURATION" + And no earlier rule matches + When the cell is rendered + Then the resulting state is PENDING_PARTNER_CREATION + # ============================================================ + # 10. STATUSES-ARRAY FALLBACK (rules 19–34) + # ============================================================ + + @motivating + Scenario Outline: Single-entry statuses array returns that single value (rule 19) + Given a reward whose pendingReasons array is empty + And reward.partnerFundsTransfer is missing + And reward.statuses is + When the cell is rendered + Then the resulting state is "" + + Examples: + | statuses | state | + | ["AVAILABLE"] | AVAILABLE | + | ["REDEEMED"] | REDEEMED | + | ["EXPIRED"] | EXPIRED | + | ["CANCELLED"] | CANCELLED | + | ["PENDING"] | PENDING | + | ["DENIED"] | DENIED | + | ["UNKNOWN_STATUS"] | UNKNOWN_STATUS | + + @motivating + Scenario Outline: Multi-entry statuses array returns the first matching possibleState (rules 20–34) + Given a reward whose pendingReasons array is empty + And reward.partnerFundsTransfer is missing + And reward.statuses is + When the cell is rendered + Then the resulting state is "" + # possibleStates priority order: + # REDEEMED > CANCELLED > EXPIRED > PENDING > AVAILABLE > PENDING_REVIEW + # > DENIED > PAYOUT_APPROVED > PAYOUT_CANCELLED > PAYOUT_FAILED + # > PENDING_TAX_REVIEW > PENDING_NEW_TAX_FORM > PENDING_TAX_SUBMISSION + # > PENDING_PARTNER_CREATION > PROCESSING + + Examples: + | statuses | state | + | ["AVAILABLE", "PENDING"] | PENDING | + | ["AVAILABLE", "REDEEMED"] | REDEEMED | + | ["EXPIRED", "PENDING"] | EXPIRED | + | ["PENDING", "AVAILABLE"] | PENDING | + | ["AVAILABLE", "DENIED"] | AVAILABLE | + | ["CANCELLED", "AVAILABLE"] | CANCELLED | + | ["EXPIRED", "CANCELLED", "REDEEMED"] | REDEEMED | + | ["PENDING_REVIEW", "DENIED", "AVAILABLE"] | AVAILABLE | + | ["PROCESSING", "PENDING_PARTNER_CREATION"] | PENDING_PARTNER_CREATION | + + @minutia + Scenario: Multi-entry statuses with no matching possibleState returns undefined + Given a reward whose pendingReasons array is empty + And reward.partnerFundsTransfer is missing + And reward.statuses is ["UNKNOWN_A", "UNKNOWN_B"] + When the cell is rendered + Then the resulting state is undefined + And the primary badge text is "Not available" + And the description body has no state-specific status line + + @minutia + Scenario: Empty statuses array with no other matches returns undefined + Given a reward whose pendingReasons array is empty + And reward.partnerFundsTransfer is missing + And reward.statuses is [] + When the cell is rendered + Then the resulting state is undefined + And the primary badge text is "Not available" + # ============================================================ + # 11. ICU CUSTOMIZATION (statusText, statusLongText) + # ============================================================ + + @motivating + Scenario Outline: statusText prop overrides the primary badge text per state + Given the "status-text" prop overrides "" to "" + And a reward whose computed state is "" + And NOT (state is PENDING and reward.dateScheduledFor is set) + When the cell is rendered + Then the primary badge text is exactly "" + + Examples: + | state | customLabel | + | AVAILABLE | Redeem me! | + | PENDING | Coming soon! | + | PAYOUT_APPROVED | Payout Sent! | + | DENIED | Unlucky! | + | PENDING_TAX_REVIEW | Tax review in progress | + | PENDING_PARTNER_CREATION | Finish setup | + + @motivating + Scenario Outline: statusLongText prop overrides the description body per state + Given the "status-long-text" prop overrides "" to "" + And a reward whose computed state is "" + And the conditional render block for "" fires + When the cell is rendered + Then the description body contains "" + + Examples: + | state | customLabel | + | DENIED | Sorry, this referral was denied | + | PAYOUT_FAILED | We were unable to send your money | + | PENDING_TAX_SUBMISSION | Please upload your W-9 form | + | PENDING_PARTNER_CREATION | Finish onboarding | + + @minutia + Scenario: statusLongText for a state without a render block has no effect on the body + Given the "status-long-text" prop overrides "REDEEMED" to "You redeemed!" + And a reward whose computed state is REDEEMED + When the cell is rendered + Then the description body does NOT contain "You redeemed!" + And only the dateGiven and fuelTankCode lines (if any) are rendered + # ============================================================ + # 12. LOCALIZATION + # ============================================================ + + @minutia + Scenario: All dates and ICU strings flow through the configured locale + Given the "locale" prop is set to a supported locale + Then dateGiven, dateExpires, dateCancelled, dateModerated, dateScheduledFor, + And relative time expressions use `DateTime.toRelative()` with the same locale, with the leading "in " stripped + And every prop-driven message string is run through `intl.formatMessage` 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/TaxAndCashRewardsTableCell.stories.tsx b/packages/mint-components/src/components/sqm-rewards-table/TaxAndCashRewardsTableCell.stories.tsx index 8dedec2ec5..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,343 +69,385 @@ const cashReward = { }, referral: null, description: null, - statuses: ["REDEEMED"], + statuses: [], rewardRedemptionTransactions: { data: null, }, exchangedRewardRedemptionTransaction: null, pendingReasons: [], + rewardedCash: true, }; -const pending = { - statuses: ["PENDING"], -}; +// ============================================================ +// Reusable tax connections +// ============================================================ -const payoutSent = { - statuses: ["PAYOUT_APPROVED"], -}; -const payoutFailed = { - statuses: ["PAYOUT_FAILED"], -}; -const payoutCancelled = { - statuses: ["PAYOUT_CANCELLED"], - dateCancelled: 1355612521321, -}; - -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: "NOT_STARTED", + 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 StatusCellPartnerCreatedSetupStartedButIncomplete = () => { - return ( - - ); -}; +// 1. Fraud check denied the referral → status is Denied +export const StatusCellFraudDenied = () => ( + +); -export const StatusCellPartnerNotCreatedSetupNotStarted = () => { - return ( - - ); -}; +// 2. Fraud check is still pending review → status is Pending Review +export const StatusCellFraudPendingReview = () => ( + +); -export const StatusCellPendingW9 = () => { - return ( - - ); -}; +// 3. Partner funds transfer was reversed → status is Payout Cancelled +export const StatusCellPayoutCancelled = () => ( + +); -export const StatusCellPayoutSent = () => { - return ( - - ); -}; +// 4. Partner funds transfer is overdue → status is Payout Failed +export const StatusCellPayoutFailed = () => ( + +); -export const StatusCellPayoutFailed = () => { - return ( - - ); -}; +// 5. Connected partner has not finished withdrawal settings setup → status is Pending +export const StatusCellPartnerCreatedSetupStartedButIncomplete = () => ( + +); -export const StatusCellPayoutProcessing = () => { - return ( - - ); -}; +// Reward is pending US_TAX and the partner has not been created yet → shows the partner-setup prompt +export const StatusCellPartnerNotCreatedSetupNotStarted = () => ( + +); -export const StatusCellPayoutCancelled = () => { - return ( - - ); -}; +// 6. Reward has MISSING_PAYOUT_CONFIGURATION pending reason and no withdrawal settings → status is Pending +export const StatusCellMissingPayoutConfiguration = () => ( + +); -export const StatusCellCancelled = () => { - return ( - - ); -}; +// 7. Partner funds transfer is scheduled for a future date → status is Processing +export const StatusCellPayoutProcessing = () => ( + +); -export const StatusCellPFTNoWithdrawalSettings = () => { - return ( - - ); -}; +// 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 = () => ( + +); -export const StatusCellNotConnectedExistingTaxForm = () => { - return ( - ( + +); + +// 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 StatusCellExistingPartnerTaxFormNoWithdrawalSettings = () => { - return ( - ( + - ); -}; + }, + }} + > +); + +// Partner has an active tax form but has not configured withdrawal settings → shows the partner-setup prompt +export const StatusCellExistingTaxFormNoWithdrawalSettings = () => ( + +); + +// 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 521ed38bbb..ecc050a9d4 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,25 +101,30 @@ 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 + // 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 partnerTransferStatus = reward.partnerFundsTransfer?.status; + const isCashReward = reward.rewardedCash; + console.log(this.reward, "reward in status cell"); - if (reward.partnerFundsTransfer) { - if (partnerTransferStatus === "REVERSED") return "PAYOUT_CANCELLED"; - if (partnerTransferStatus === "OVERDUE") return "PAYOUT_FAILED"; - } // pft can now be created before withdrawal settings exist - const needsPayoutSetup = - this.taxConnection?.connected && - !this.taxConnection?.publisher?.withdrawalSettings; - if (needsPayoutSetup) return "PENDING"; + 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() @@ -191,11 +196,13 @@ export class RewardTableStatusCell { // 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 ( - taxConnection?.connected && - !taxConnection?.publisher?.withdrawalSettings - ) { - return this.pendingPartnerCreation; + if (reward.rewardedCash) { + if ( + !taxConnection?.connected || + (taxConnection?.connected && + !taxConnection?.publisher?.withdrawalSettings) + ) + return this.pendingPartnerCreation; } return ""; diff --git a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature index bc528d5c24..1df30a794f 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature +++ b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature @@ -43,25 +43,72 @@ Feature: Reward Table Status Column Scenario: Status precedence ladder Given a reward, its referral fraud state, the user's Impact tax connection Then the displayed status is determined in the following order: - | order | rule | resulting status | resulting description | - | 1 | referral.fraudData.moderationStatus is "DENIED" | DENIED | Detected self-referral | - | 2 | referral.fraudData.moderationStatus is "PENDING" | PENDING_REVIEW | Awaiting review | - | 3 | partnerFundsTransfer.status is "REVERSED" | PAYOUT_CANCELLED | If you think this is a mistake, contact our Support team. | - | 4 | partnerFundsTransfer.status is "OVERDUE" | PAYOUT_FAILED | Payout failed due to a fulfillment issue and is currently being retried. | - | 5 | impactConnection is connected AND publisher.withdrawalSettings is missing | PENDING | Complete your tax and cash payout setup to receive your rewards. | - | 6 | reward.pendingReasons includes "MISSING_PAYOUT_CONFIGURATION" AND publisher.withdrawalSettings is missing | PENDING | Complete your tax and cash payout setup to receive your rewards. | - | 7 | partnerFundsTransfer.dateScheduled is in the future | 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 and scheduled for payment based on your settings. | - | 9 | reward.dateCancelled is set | CANCELLED | Jan 1, 2026 | - | 10 | reward.statuses includes "EXPIRED" | EXPIRED | Jan 1, 2026 | - | 11 | reward.statuses includes "PENDING" | PENDING | Until Jan 1, 2026 | - | 12 | reward.type is "CREDIT" AND statuses includes "REDEEMED" | REDEEMED | Jan 1, 2026 | - | 13 | reward.type is "CREDIT" | AVAILABLE | Expires Jan 1, 2026 | - | 14 | reward.type is "PCT_DISCOUNT" AND statuses includes "AVAILABLE" | AVAILABLE | Expires Jan 1, 2026 | - | 15 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "PENDING" | PENDING | Until Jan 1, 2026 | - | 16 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "CANCELLED" | CANCELLED | Jan 1, 2026 | - | 17 | reward.type is "INTEGRATION" / "FUELTANK" AND statuses includes "AVAILABLE" | AVAILABLE | Expires Jan 1, 2026 | - | 18 | none of the above | "" (no badge text) | (no description) | + | order | rule | resulting status | resulting badge text | resulting description | + | 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 | + | 16 | none of the above | "" (no badge text) | (no badge text) | (no description) | + But for rewards where reward.rewardedCash is false (non-cash rewards, e.g. CREDIT/POINTS, PCT_DISCOUNT, INTEGRATION, FUELTANK), the following non-cash-only rules are inserted between rule 12 and rule 13, in this order: + | order | rule | resulting status | resulting badge text | resulting description | + | 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 | + + @motivating + Scenario: PENDING description precedence ladder + Given a PENDING reward, its pendingReasons, and the user's Impact tax connection + Then the description shown under the Pending pill is determined in the following order: + | order | rule | resulting status | resulting badge text | resulting description | + | 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. | + + @motivating @ui + Scenario Outline: Payout descriptions for PFT-derived statuses + Given a reward whose computed status is + When the cell is rendered + Then the description line shows + + Examples: + | status | description | + | PAYOUT_APPROVED | Payout approved and scheduled for payment 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 . Payout is then scheduled based on your settings. | + + @motivating @ui + Scenario Outline: US_TAX pending reason resolves based on the tax connection state + Given the user has a PENDING reward whose pendingReasons include "US_TAX" + And the user's impactConnection.taxHandlingEnabled is + And the user's impactConnection.connected is + And the user's publisher.requiredTaxDocumentType is + And the user's publisher.currentTaxDocument is + And the user's publisher.currentTaxDocument.status is + And the user's publisher.withdrawalSettings is + Then the badge is shown in a pill with text + And the description line shows + + Examples: + | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | badgeText | pillColour | description | + | false | true | W9 | present | ACTIVE | present | Pending | warning | W-9 required | + | true | false | - | - | - | - | Pending | warning | Complete your tax and cash payout setup to receive your rewards. | + | true | true | W9 | missing | - | present | Pending | warning | Submit your tax documents to receive your rewards. | + | true | true | W9 | present | INACTIVE | present | Pending | warning | Invalid tax form. Submit a new form to receive your rewards. | + | true | true | W9 | present | NOT_VERIFIED | present | Pending | warning | Awaiting tax form review. | + | true | true | W9 | present | ACTIVE | missing | Pending | warning | Complete your tax and cash payout setup to receive your rewards. | # ============================================================ # 3. STATUS → BADGE COLOUR MAPPING # ============================================================ @@ -73,18 +120,19 @@ Feature: Reward Table Status Column Then the badge is shown in a pill with Examples: - | status | text | pillColour | - | AVAILABLE | Available | success | - | REDEEMED | Redeemed | primary | - | PAYOUT_APPROVED | Payout Approved | primary | - | PENDING | Pending | warning | - | PENDING_REVIEW | Pending | warning | - | PROCESSING | Processing | warning | - | CANCELLED | Cancelled | danger | - | EXPIRED | Expired | danger | - | DENIED | Denied | danger | - | PAYOUT_FAILED | Payout Failed | danger | - | PAYOUT_CANCELLED | Payout Cancelled | danger | + | status | text | pillColour | + | AVAILABLE | Available | success | + | REDEEMED | Redeemed | primary | + | PAYOUT_APPROVED | Payout Approved | primary | + | PENDING | Pending | warning | + | PENDING_REVIEW | Pending | warning | + | PROCESSING | Payment Processing | warning | + | CANCELLED | Cancelled | danger | + | EXPIRED | Expired | danger | + | DENIED | Denied | danger | + | PAYOUT_FAILED | Payout Failed | danger | + | PAYOUT_CANCELLED | Payout Cancelled | danger | + | (other / "") | Not available | danger | # ============================================================ # 4. DESCRIPTION LINE PRECEDENCE # The description under the badge is selected in this order: @@ -120,59 +168,15 @@ Feature: Reward Table Status Column | EXPIRED | dateExpires | | | CANCELLED | dateCancelled | | | REDEEMED | dateRedeemed | | - - @motivating @ui - Scenario Outline: Payout descriptions for PFT-derived statuses - Given a reward whose computed status is - When the cell is rendered - Then the description line shows - - Examples: - | status | description | - | PAYOUT_APPROVED | Payout approved and scheduled for payment 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 . Payout is then scheduled based on your settings. | # ============================================================ - # 5. PENDING DESCRIPTION RESOLUTION (getTaxPendingReasons) - # When the computed status is PENDING the description is the first - # non-empty value of: - # a) tax/payout reason (getTaxPendingReasons) - # b) reward.pendingReasons mapped through pendingCodeMap, joined by ", " + # 5. PENDING DESCRIPTION PRECEDENCE + # Once the status precedence ladder in § 2 has produced PENDING, the + # description shown under the "Pending" pill is determined by the + # following ordered ladder (mirrors getTaxPendingReasons + the + # pendingCodeMap fallback). All rules render a warning pill with text + # "Pending"; only the description varies. # ============================================================ - @motivating @ui - Scenario Outline: US_TAX pending reason resolves based on the tax connection state - Given the user has a PENDING reward whose pendingReasons include "US_TAX" - And the user's impactConnection.taxHandlingEnabled is - And the user's impactConnection.connected is - And the user's publisher.requiredTaxDocumentType is - And the user's publisher.currentTaxDocument is - And the user's publisher.currentTaxDocument.status is - And the user's publisher.withdrawalSettings is - Then the description line shows - - Examples: - | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | description | - | false | true | W9 | present | ACTIVE | present | W-9 required | - | true | false | - | - | - | - | Complete your tax and cash payout setup to receive your rewards. | - | true | true | W9 | missing | - | present | Submit your tax documents to receive your rewards. | - | true | true | W9 | present | INACTIVE | present | Invalid tax form. Submit a new form to receive your rewards. | - | true | true | W9 | present | NOT_VERIFIED | present | Awaiting tax form review. | - | true | true | W9 | present | ACTIVE | missing | Complete your tax and cash payout setup to receive your rewards. | - - @motivating @ui - Scenario: MISSING_PAYOUT_CONFIGURATION pending reason shows the partner-creation prompt - Given the user has a PENDING reward whose pendingReasons include "MISSING_PAYOUT_CONFIGURATION" - And the user is connected with publisher.withdrawalSettings missing - Then the description line shows "Complete your tax and cash payout setup to receive your rewards." - - @motivating @ui - Scenario: Connected user without withdrawal settings shows the partner-creation prompt as a fallback - Given the user has a PENDING reward whose pendingReasons array is empty - And the user is connected with publisher.withdrawalSettings missing - Then the description line shows "Complete your tax and cash payout setup to receive your rewards." - @motivating @ui Scenario Outline: Generic pendingReasons codes are mapped via pendingCodeMap Given the user has a PENDING reward 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/saasquatch.d.ts b/packages/mint-components/src/saasquatch.d.ts index 6fadd6eab5..cd1b4fbcfa 100644 --- a/packages/mint-components/src/saasquatch.d.ts +++ b/packages/mint-components/src/saasquatch.d.ts @@ -146,6 +146,7 @@ interface Reward { } ]; }; + rewardedCash?: boolean; } interface ReferralVariables { From b614540e4726033b53ed4d5413d3d8ad71d9f7c9 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 13 May 2026 15:05:36 -0700 Subject: [PATCH 29/64] update specs" --- .../referral-table-rewards-column-new.feature | 49 ++++--- .../referral-table-rewards-column.feature | 127 +++++++++++------- ...qm-rewards-table-status-column-new.feature | 42 +++--- .../sqm-rewards-table-status-column.feature | 44 ++++++ 4 files changed, 166 insertions(+), 96 deletions(-) diff --git a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature index 8505ece74f..d1b512a8be 100644 --- a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature +++ b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature @@ -122,32 +122,29 @@ Feature: Referral Table Rewards Cell @motivating Scenario: State precedence ladder Given a reward, its referral's fraud state, and the user's tax connection - And dummy data is used where applicable: dateModerated/dateExpires/dateCancelled/dateScheduledFor/partnerFundsTransfer.dateScheduled = Jan 1, 2026; deniedHelpText = "Contact support." - Then the displayed state is determined in the following order, returning at the first match: - | order | rule | resulting state | resulting badge text | resulting description body | - | 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 | - | 19 | none of the above | undefined | Not available | (no state-specific body line) | - But for rewards where reward.rewardedCash is false (non-cash rewards, e.g. CREDIT/POINTS, PCT_DISCOUNT, INTEGRATION, FUELTANK — these skip rules 3a/3b entirely and never carry a partnerFundsTransfer or US_TAX/MISSING_PAYOUT_CONFIGURATION pendingReason, so they always fall through to the reward.statuses fallback), the following non-cash-only rules are inserted between rule 14 and rule 18, in this order: - | order | rule | resulting state | resulting badge text | resulting description body | - | 15 | statuses includes "REDEEMED" | REDEEMED | Redeemed | (no state-specific body line) | - | 16 | statuses includes "CANCELLED" | CANCELLED | Cancelled | Reward cancelled on Jan 1, 2026 | - | 17 | statuses includes "EXPIRED" | EXPIRED | Expired | Reward expired on Jan 1, 2026 | - | 17a | 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) | - | 17b | statuses includes "AVAILABLE" | AVAILABLE | Available (plus info pill "{expiringText} {relative}" if dateExpires set) | Reward expiring on Jan 1, 2026 | + Then is produced + And the , , and is determined in the following order: + | order | rule | resulting state | resulting badge text | resulting description body | + | 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 Scenario: Cash reward with connected partner but no withdrawal settings overrides any PFT-derived state 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/columns/sqm-rewards-table-status-column-new.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature index 1df30a794f..fbe54a04b4 100644 --- a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature +++ b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature @@ -42,26 +42,26 @@ Feature: Reward Table Status Column @motivating Scenario: Status precedence ladder Given a reward, its referral fraud state, the user's Impact tax connection - Then the displayed status is determined in the following order: - | order | rule | resulting status | resulting badge text | resulting description | - | 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 | - | 16 | none of the above | "" (no badge text) | (no badge text) | (no description) | - But for rewards where reward.rewardedCash is false (non-cash rewards, e.g. CREDIT/POINTS, PCT_DISCOUNT, INTEGRATION, FUELTANK), the following non-cash-only rules are inserted between rule 12 and rule 13, in this order: - | order | rule | resulting status | resulting badge text | resulting description | - | 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 | + Then is produced + And the , , and is determined in the following order: + | order | rule | resulting status | resulting badge text | resulting description | + | 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: PENDING description precedence ladder @@ -75,6 +75,8 @@ Feature: Reward Table Status Column | 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 @ui Scenario Outline: Payout descriptions for PFT-derived statuses 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 8509e4bae9..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 From 385dddc9038d7483d2b5befa6e876f9d4b00a56b Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 21 May 2026 09:19:14 -0700 Subject: [PATCH 30/64] Remove draft spec files --- .../referral-table-rewards-column-new.feature | 777 ------------------ ...qm-rewards-table-status-column-new.feature | 240 ------ 2 files changed, 1017 deletions(-) delete mode 100644 packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature delete mode 100644 packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature diff --git a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature b/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature deleted file mode 100644 index d1b512a8be..0000000000 --- a/packages/mint-components/src/components/sqm-referral-table/columns/referral-table-rewards-column-new.feature +++ /dev/null @@ -1,777 +0,0 @@ -@author:derek @owner:derek -Feature: Referral Table Rewards Cell - Renders the rewards earned for each referral as an `` block. - Each reward shows: - - the reward value (e.g. "$50.00") in the summary - - a coloured badge "pill" with status text - - optionally a second "info" pill (only for AVAILABLE rewards with an expiry) - - a description body that is a STACK of zero or more conditional lines: - 1. a state-specific status line (only for some states) - 2. a "reward received" line (only when reward.dateGiven is set) - 3. a fuel-tank code line (only when reward.fuelTankCode is set) - The state shown is computed by `getState()` from: - 1. the referral's fraud moderation status, - 2. the user's Impact tax/payout connection (taxConnection prop), - 3. the reward's PartnerFundsTransfer (PFT), - 4. the reward's `pendingReasons` (US_TAX, MISSING_PAYOUT_CONFIGURATION), - 5. the reward's `statuses` array (fallback). - - Background: - Given the user is viewing the referral table - And each row may show one or more rewards via sqm-referral-table-rewards-cell - And the reward value is always shown in the summary as the bold prettyValue (e.g. "$50.00") - # ============================================================ - # 1. CELL CONFIGURATION (every prop in isolation) - # ============================================================ - - @motivating @ui - Scenario: hideDetails=true hides the disclosure caret and disables expansion - Given the "hide-details" prop is true - When the cell is rendered - Then the sl-details summary-icon is set to display:none - And the sl-details element is rendered with the disabled attribute - And the summary cursor style is "default" - - @motivating @ui - Scenario: hideDetails=false (default) shows the disclosure caret and allows expansion - Given the "hide-details" prop is false - When the cell is rendered - Then the sl-details summary-icon is set to display:flex - And the summary cursor style is "pointer" - - @minutia @ui - Scenario Outline: rewardReceivedText prop drives the reward-received line - Given a reward with dateGiven Jan 1, 2026 - And the "reward-received-text" prop is "" - When the cell is rendered - Then the description body contains " Jan 1, 2026" with "Jan 1, 2026" rendered in bold - - Examples: - | value | - | Reward given | - | You earned on | - | (empty string) | - - @minutia @ui - Scenario Outline: expiringText prop drives the AVAILABLE info pill - Given an AVAILABLE reward with dateExpires Aug 31, 2026 - And the current date is May 7, 2026 - And the "expiring-text" prop is "" - When the cell is rendered - Then the second info pill text is exactly "" - - Examples: - | value | rendered | - | Expiring | Expiring 3 months | - | Expires | Expires 3 months | - | (unset) | undefined 3 months | - - @minutia @ui - Scenario Outline: pendingForText prop drives the badge text for PENDING+dateScheduledFor - Given a PENDING reward with dateScheduledFor Aug 31, 2026 - And the current date is May 7, 2026 - And the "pending-for-text" prop is "" - When the cell is rendered - Then the primary badge text is exactly "" - - Examples: - | value | rendered | - | {status} for {date} | Pending for 3 months | - | Available {date} | Available 3 months | - - @motivating @ui - Scenario: deniedHelpText prop is appended to the DENIED description with a leading space - Given a DENIED reward with referral.dateModerated Jan 1, 2026 - And the "denied-help-text" prop is "Contact support if you believe this is an error." - When the cell is rendered - Then the description body contains "Denied on Jan 1, 2026. Contact support if you believe this is an error." - - @motivating @ui - Scenario: deniedHelpText prop omitted leaves the DENIED description with only the trailing period - Given a DENIED reward with referral.dateModerated Jan 1, 2026 - And the "denied-help-text" prop is unset - When the cell is rendered - Then the description body contains "Denied on Jan 1, 2026." - And the description body does NOT contain any text after that period - - @motivating @ui - Scenario: fuelTankText prop drives the fuel-tank line - Given a reward with fuelTankCode "ABC-123" - And the "fuel-tank-text" prop is "Your code:" - When the cell is rendered - Then the description body contains "Your code: ABC-123" with "ABC-123" rendered in bold - - @minutia @ui - Scenario: locale prop changes the date formatter and relative time formatter - Given the "locale" prop is set to a supported locale - Then every Luxon date is formatted using `luxonLocale(locale)` with `DateTime.DATE_MED` - And every relative time uses `DateTime.toRelative()` with the same locale, with the leading "in " stripped - And every prop-driven message string is run through `intl.formatMessage` - # ============================================================ - # 2. STATE PRECEDENCE LADDER - # `getState()` walks the rules below from top to bottom and returns - # at the FIRST matching rule. Note: rules 3a and 3b (cash-reward payout- - # setup gate) fire BEFORE any PFT branch, so they suppress PFT-derived - # PROCESSING / PAYOUT_APPROVED states whenever the user has a cash - # reward but has not finished tax/payout setup (either not connected at - # all, or connected without withdrawalSettings). For cash rewards, - # PAYOUT_CANCELLED (REVERSED) and PAYOUT_FAILED (OVERDUE) are - # unreachable past rules 3a/3b when those gates fire. - # ============================================================ - - @motivating - Scenario: 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 order: - | order | rule | resulting state | resulting badge text | resulting description body | - | 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 - Scenario: Cash reward with connected partner but no withdrawal settings overrides any PFT-derived state - Given a cash reward (reward.rewardedCash is true) with partnerFundsTransfer status "TRANSFERRED" and dateScheduled in the past - And taxConnection.connected is true - And taxConnection.publisher.withdrawalSettings is missing - When the cell is rendered - Then the resulting state is PENDING_PARTNER_CREATION - - @motivating - Scenario: Cash reward with no impact connection overrides any PFT-derived state - Given a cash reward (reward.rewardedCash is true) with partnerFundsTransfer status "TRANSFERRED" and dateScheduled in the past - And taxConnection.connected is false - When the cell is rendered - Then the resulting state is PENDING_PARTNER_CREATION - - @motivating - Scenario: Non-cash reward skips the payout-setup gate - Given a non-cash reward (reward.rewardedCash is false) with reward.statuses ["AVAILABLE"] - And taxConnection.connected is false - When the cell is rendered - Then the resulting state is AVAILABLE - - @minutia - Scenario: Connected partner with no withdrawal settings does NOT override fraud states - Given a reward whose referral.fraudData.moderationStatus is "DENIED" - And taxConnection.connected is true - And taxConnection.publisher.withdrawalSettings is missing - When the cell is rendered - Then the resulting state is DENIED - # ============================================================ - # 3. STATE → BADGE COLOUR (`getSLBadgeType`) - # ============================================================ - - @motivating @ui - Scenario Outline: Each state maps to a Shoelace badge type - Given a reward whose computed state is "" - When the cell is rendered - Then the primary pill is rendered with type "" - And the primary pill is rendered with the "" CSS class - # CSS class applied: primary→RedeemBadge, danger→DangerBadge, - # warning→WarningBadge, success→SuccessBadge, info→WarningBadge. - # Note: getSLBadgeType returns undefined for any state outside the cases - # below; the resulting CSS class then defaults to WarningBadge. - - Examples: - | state | slBadgeType | cssClass | - | REDEEMED | primary | RedeemBadge | - | PAYOUT_APPROVED | primary | RedeemBadge | - | DENIED | danger | DangerBadge | - | EXPIRED | danger | DangerBadge | - | CANCELLED | danger | DangerBadge | - | PAYOUT_FAILED | danger | DangerBadge | - | PAYOUT_CANCELLED | danger | DangerBadge | - | PENDING | warning | WarningBadge | - | PENDING_REVIEW | warning | WarningBadge | - | PENDING_TAX_REVIEW | warning | WarningBadge | - | PENDING_NEW_TAX_FORM | warning | WarningBadge | - | PENDING_TAX_SUBMISSION | warning | WarningBadge | - | PENDING_PARTNER_CREATION | warning | WarningBadge | - | PROCESSING | warning | WarningBadge | - | AVAILABLE | success | SuccessBadge | - | (empty string "") | undefined | WarningBadge | - | (any unknown string) | undefined | WarningBadge | - # ============================================================ - # 4. PRIMARY BADGE TEXT - # The primary badge ALWAYS renders. Its text comes from one of two paths: - # - "pendingFor" overlay: state==="PENDING" AND reward.dateScheduledFor truthy - # - default: intl.formatMessage(statusText, {status: state}) - # The default `statusText` ICU message maps every state below. - # ============================================================ - - @motivating @ui - Scenario Outline: Default primary badge text per state (no PENDING+dateScheduledFor overlay) - Given a reward whose computed state is "" - And NOT (state is PENDING and reward.dateScheduledFor is set) - And the "status-text" prop is the default - When the cell is rendered - Then the primary badge text is exactly "" - - Examples: - | state | text | - | 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 | - | (empty string "") | Not available | - | (any unknown string) | Not available | - - @motivating @ui - Scenario: PENDING + dateScheduledFor uses the pendingForText overlay (default ICU) - Given a PENDING reward with dateScheduledFor Aug 31, 2026 - And the current date is May 7, 2026 - And the "status-text" prop is the default - And the "pending-for-text" prop is the default "{status} for {date}" - When the cell is rendered - Then the primary badge text is exactly "Pending for 3 months" - - @motivating @ui - Scenario: PENDING WITHOUT dateScheduledFor falls back to the default badge text - Given a PENDING reward whose dateScheduledFor is unset - When the cell is rendered - Then the primary badge text is exactly "Pending" - - @minutia @ui - Scenario: pendingForText overlay is suppressed for non-PENDING states even with dateScheduledFor - Given an AVAILABLE reward whose dateScheduledFor is set Aug 31, 2026 - When the cell is rendered - Then the primary badge text is exactly "Available" - And the pendingForText template is NOT applied - # ============================================================ - # 5. SECONDARY ("INFO") PILL - # The second pill renders ONLY when state==="AVAILABLE" AND reward.dateExpires is truthy. - # Its text is `${expiringText} ${getTimeDiff(dateExpires)}` — note the leading space - # always present and the prop value is rendered as-is (undefined → literal "undefined ..."). - # ============================================================ - - @motivating @ui - Scenario Outline: Secondary info pill renders only for AVAILABLE+dateExpires - Given a reward whose computed state is "" - And reward.dateExpires is - When the cell is rendered - Then the secondary info pill is rendered: - - Examples: - | state | dateExpires | rendered | - | AVAILABLE | Aug 31, 2026 (set) | yes | - | AVAILABLE | unset | no | - | EXPIRED | Aug 31, 2026 (set) | no | - | REDEEMED | Aug 31, 2026 (set) | no | - | PENDING | Aug 31, 2026 (set) | no | - | CANCELLED | Aug 31, 2026 (set) | no | - | PENDING_REVIEW | Aug 31, 2026 (set) | no | - | DENIED | Aug 31, 2026 (set) | no | - | PAYOUT_APPROVED | Aug 31, 2026 (set) | no | - | PROCESSING | Aug 31, 2026 (set) | no | - - @motivating @ui - Scenario Outline: Secondary info pill text composition - Given an AVAILABLE reward with dateExpires Aug 31, 2026 - And the current date is May 7, 2026 - And the "expiring-text" prop is - When the cell is rendered - Then the secondary info pill is rendered with type "info" - And the secondary info pill is rendered with the WarningBadge CSS class - And the secondary info pill text is exactly "" - - Examples: - | expiringText | text | - | "Expiring" | Expiring 3 months | - | "" | 3 months | - | unset | undefined 3 months | - # ============================================================ - # 6. DESCRIPTION BODY — PER-STATE STATUS LINE - # The body is a stack of conditional render blocks, evaluated in source order: - # PENDING_REVIEW → "" → PROCESSING → PAYOUT_FAILED → PAYOUT_CANCELLED → - # PENDING_TAX_REVIEW → PENDING_NEW_TAX_FORM → PENDING_TAX_SUBMISSION → - # PENDING_PARTNER_CREATION → DENIED → dateGiven → EXPIRED → CANCELLED → - # PENDING → AVAILABLE → fuelTankCode. - # All other states (REDEEMED, etc.) render NO state-specific - # status line — only the dateGiven/fuelTankCode lines below if applicable. - # ============================================================ - - @motivating @ui - Scenario: PENDING_REVIEW with dateModerated renders the "Pending since" line - Given a reward whose computed state is PENDING_REVIEW - And reward.referral.dateModerated is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Pending since Jan 1, 2026" with "Jan 1, 2026" in bold - - @motivating @ui - Scenario: PENDING_REVIEW WITHOUT dateModerated renders no "Pending since" line - Given a reward whose computed state is PENDING_REVIEW - And reward.referral.dateModerated is unset - When the cell is rendered - Then the description body does NOT contain a "Pending since" line - - @motivating @ui - Scenario: state === "" renders the bare "Not available" statusText line - Given a reward whose computed state is the empty string "" - When the cell is rendered - Then the description body contains exactly the text "Not available" - - @minutia @ui - Scenario: state === undefined does NOT render the "Not available" line in the body - Given a reward whose computed state is undefined - When the cell is rendered - Then the description body does NOT contain "Not available" - And only the dateGiven and fuelTankCode lines (if any) are rendered - - @motivating @ui - Scenario: PROCESSING renders the "Processing until {date}." line - Given a reward whose computed state is PROCESSING - And reward.partnerFundsTransfer.dateScheduled is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Processing until Jan 1, 2026. Payout is then scheduled based on your settings." - - @motivating @ui - Scenario: PAYOUT_APPROVED renders the "Processing until {date}." line - Given a reward whose computed state is PAYOUT_APPROVED - And reward.partnerFundsTransfer.dateScheduled is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Processing until Jan 1, 2026. Payout is then scheduled based on your settings." - - @motivating @ui - Scenario: PAYOUT_FAILED renders the failed-fulfillment line - Given a reward whose computed state is PAYOUT_FAILED - When the cell is rendered - Then the description body contains "Payout failed due to a fulfillment issue and is currently being retried." - - @motivating @ui - Scenario: PAYOUT_CANCELLED renders the contact-support line - Given a reward whose computed state is PAYOUT_CANCELLED - When the cell is rendered - Then the description body contains "If you think this is a mistake, contact our Support team." - - @motivating @ui - Scenario: PENDING_TAX_REVIEW renders the tax-review line - Given a reward whose computed state is PENDING_TAX_REVIEW - When the cell is rendered - Then the description body contains "Awaiting tax form review" - - @motivating @ui - Scenario: PENDING_NEW_TAX_FORM renders the invalid-tax-form line - Given a reward whose computed state is PENDING_NEW_TAX_FORM - When the cell is rendered - Then the description body contains "Invalid tax form. Submit a new form to receive your rewards." - - @motivating @ui - Scenario: PENDING_TAX_SUBMISSION renders the submit-tax-documents line - Given a reward whose computed state is PENDING_TAX_SUBMISSION - When the cell is rendered - Then the description body contains "Submit your tax documents to receive your rewards" - - @motivating @ui - Scenario: PENDING_PARTNER_CREATION renders the complete-payout-setup line - Given a reward whose computed state is PENDING_PARTNER_CREATION - When the cell is rendered - Then the description body contains "Complete your tax and cash payout setup to receive your rewards" - - @motivating @ui - Scenario: DENIED with dateModerated renders "Denied on {date}." with trailing period - Given a reward whose computed state is DENIED - And reward.referral.dateModerated is Jan 1, 2026 - And the "denied-help-text" prop is unset - When the cell is rendered - Then the description body contains "Denied on Jan 1, 2026." with "Jan 1, 2026" in bold - - @motivating @ui - Scenario: DENIED with dateModerated AND deniedHelpText appends help text after the period - Given a reward whose computed state is DENIED - And reward.referral.dateModerated is Jan 1, 2026 - And the "denied-help-text" prop is "Contact our support team." - When the cell is rendered - Then the description body contains "Denied on Jan 1, 2026. Contact our support team." - - @motivating @ui - Scenario: DENIED WITHOUT dateModerated renders no "Denied on" line - Given a reward whose computed state is DENIED - And reward.referral.dateModerated is unset - When the cell is rendered - Then the description body does NOT contain a "Denied on" line - And the deniedHelpText is NOT rendered (it lives inside the dateModerated block) - - @motivating @ui - Scenario: EXPIRED with dateExpires renders the "Reward expired on {date}" line - Given a reward whose computed state is EXPIRED - And reward.dateExpires is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Reward expired on Jan 1, 2026" with "Jan 1, 2026" in bold - - @motivating @ui - Scenario: EXPIRED WITHOUT dateExpires renders no expired line - Given a reward whose computed state is EXPIRED - And reward.dateExpires is unset - When the cell is rendered - Then the description body does NOT contain "Reward expired on" - - @motivating @ui - Scenario: CANCELLED with dateCancelled renders the "Reward cancelled on {date}" line - Given a reward whose computed state is CANCELLED - And reward.dateCancelled is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Reward cancelled on Jan 1, 2026" with "Jan 1, 2026" in bold - - @motivating @ui - Scenario: CANCELLED WITHOUT dateCancelled renders no cancelled line - Given a reward whose computed state is CANCELLED - And reward.dateCancelled is unset - When the cell is rendered - Then the description body does NOT contain "Reward cancelled on" - - @motivating @ui - Scenario: PENDING with dateScheduledFor renders the "Available on {date}" line - Given a reward whose computed state is PENDING - And reward.dateScheduledFor is Jan 1, 2026 - When the cell is rendered - Then the description body contains "Available on Jan 1, 2026" with "Jan 1, 2026" in bold - - @motivating @ui - Scenario: PENDING WITHOUT dateScheduledFor renders no "Available on" line - Given a reward whose computed state is PENDING - And reward.dateScheduledFor is unset - When the cell is rendered - Then the description body does NOT contain "Available on" - And the badge falls back to plain "Pending" - - @motivating @ui - Scenario: AVAILABLE with dateExpires renders the "Reward expiring on {date}" body line - Given a reward whose computed state is AVAILABLE - And reward.dateExpires is Aug 31, 2026 - When the cell is rendered - Then the description body contains "Reward expiring on Aug 31, 2026" with "Aug 31, 2026" in bold - - @motivating @ui - Scenario: AVAILABLE WITHOUT dateExpires renders no body line and no info pill - Given a reward whose computed state is AVAILABLE - And reward.dateExpires is unset - When the cell is rendered - Then the description body does NOT contain "Reward expiring on" - And the secondary info pill is NOT rendered - - @motivating @ui - Scenario: REDEEMED renders no state-specific body line - Given a reward whose computed state is REDEEMED - When the cell is rendered - Then the description body has NO state-specific status line - And only the dateGiven and fuelTankCode lines (if any) are rendered - # ============================================================ - # 7. ALWAYS-ON BODY LINES (dateGiven + fuelTankCode) - # These render REGARDLESS of state, layered on top of (or instead of) - # the per-state status line. - # ============================================================ - - @motivating @ui - Scenario Outline: dateGiven line renders for every state when reward.dateGiven is truthy - Given a reward with dateGiven Jan 1, 2026 - And the "reward-received-text" prop is "Reward given" - And the reward's computed state is "" - When the cell is rendered - Then the description body contains "Reward given Jan 1, 2026" with "Jan 1, 2026" in bold - - Examples: - | state | - | AVAILABLE | - | REDEEMED | - | PENDING | - | PENDING_REVIEW | - | EXPIRED | - | CANCELLED | - | DENIED | - | PAYOUT_APPROVED | - | PAYOUT_CANCELLED | - | PAYOUT_FAILED | - | PROCESSING | - | PENDING_TAX_REVIEW | - | PENDING_NEW_TAX_FORM | - | PENDING_TAX_SUBMISSION | - | PENDING_PARTNER_CREATION | - - @motivating @ui - Scenario: dateGiven line is suppressed when reward.dateGiven is unset - Given a reward whose dateGiven is unset - When the cell is rendered - Then the description body does NOT contain a reward-received line - - @motivating @ui - Scenario Outline: fuelTankCode line renders for every state when reward.fuelTankCode is truthy - Given a reward with fuelTankCode "ABC-123" - And the "fuel-tank-text" prop is "Code:" - And the reward's computed state is "" - When the cell is rendered - Then the description body contains "Code: ABC-123" with "ABC-123" in bold - - Examples: - | state | - | AVAILABLE | - | REDEEMED | - | PENDING | - | PENDING_REVIEW | - | EXPIRED | - | CANCELLED | - | DENIED | - | PAYOUT_APPROVED | - | PAYOUT_CANCELLED | - | PAYOUT_FAILED | - | PROCESSING | - | PENDING_TAX_REVIEW | - | PENDING_NEW_TAX_FORM | - | PENDING_TAX_SUBMISSION | - | PENDING_PARTNER_CREATION | - - @motivating @ui - Scenario: fuelTankCode line is suppressed when reward.fuelTankCode is unset - Given a reward whose fuelTankCode is unset - When the cell is rendered - Then the description body does NOT contain a fuel-tank line - - @minutia @ui - Scenario: Stacking order in the description body - Given an AVAILABLE reward with dateExpires Aug 31, 2026, dateGiven Jan 1, 2026, fuelTankCode "ABC-123" - When the cell is rendered - Then the description body lines appear in this order: - | order | line | - | 1 | Reward given Jan 1, 2026 (dateGiven block) | - | 2 | Reward expiring on Aug 31, 2026 (AVAILABLE block) | - | 3 | ABC-123 (fuelTankCode block) | - # ============================================================ - # 8. PARTNER FUNDS TRANSFER (PFT) MATRIX - # Every PFT permutation, with the cash-reward "needsPayoutSetup" gate - # (rules 3a/3b above) toggled on/off. The gate fires BEFORE PFT branches - # for cash rewards, but rules 4–5 (REVERSED, OVERDUE) are unreachable - # when the gate fires because the gate already returned - # PENDING_PARTNER_CREATION. - # ============================================================ - - @motivating - Scenario Outline: PFT permutations when withdrawalSettings ARE present (gate does not fire) - Given taxConnection.connected is true - And taxConnection.publisher.withdrawalSettings is present - And the reward has a partnerFundsTransfer with status and dateScheduled - When the cell is rendered - Then the resulting state is "" - - Examples: - | pftStatus | pftDateScheduled | state | - | REVERSED | (any) | PAYOUT_CANCELLED | - | OVERDUE | (any) | PAYOUT_FAILED | - | TRANSFERRED | future | PROCESSING | - | TRANSFERRED | past | PAYOUT_APPROVED | - | TRANSFERRED | unset | PAYOUT_APPROVED | - | NOT_YET_DUE | future | PROCESSING | - | NOT_YET_DUE | past | PAYOUT_APPROVED | - | NOT_YET_DUE | unset | PAYOUT_APPROVED | - | (null) | future | PROCESSING | - | (null) | past | PAYOUT_APPROVED | - - @motivating - Scenario Outline: PFT permutations when withdrawalSettings are MISSING (gate fires first) - Given taxConnection.connected is true - And taxConnection.publisher.withdrawalSettings is missing - And the reward has a partnerFundsTransfer with status and dateScheduled - When the cell is rendered - Then the resulting state is "" - - Examples: - | pftStatus | pftDateScheduled | state | - | REVERSED | (any) | PENDING_PARTNER_CREATION | - | OVERDUE | (any) | PENDING_PARTNER_CREATION | - | TRANSFERRED | future | PENDING_PARTNER_CREATION | - | TRANSFERRED | past | PENDING_PARTNER_CREATION | - | NOT_YET_DUE | future | PENDING_PARTNER_CREATION | - | NOT_YET_DUE | past | PENDING_PARTNER_CREATION | - | (null) | future | PENDING_PARTNER_CREATION | - | (null) | past | PENDING_PARTNER_CREATION | - - @motivating - Scenario: PFT branch is skipped entirely when reward.partnerFundsTransfer is missing - Given the reward has no partnerFundsTransfer - And the reward has no fraud / pendingReasons / statuses overrides - When the cell is rendered - Then PFT rules 4–9 are skipped - And the resulting state is determined by the next applicable rule - # ============================================================ - # 9. US_TAX PENDING-REASON MATRIX - # Every permutation of taxConnection state when reward.pendingReasons - # includes "US_TAX". Once US_TAX matches a sub-rule, no later rule fires. - # ============================================================ - - @motivating @ui - Scenario Outline: US_TAX pending matrix - Given a reward whose pendingReasons include "US_TAX" - And taxConnection.taxHandlingEnabled is - And taxConnection.connected is - And taxConnection.publisher.requiredTaxDocumentType is - And taxConnection.publisher.currentTaxDocument is - And taxConnection.publisher.currentTaxDocument.status is - And taxConnection.publisher.withdrawalSettings is - When the cell is rendered - Then the resulting state is "" - - Examples: - | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | state | - | false | (any) | (any) | (any) | (any) | (any) | PENDING | - | true | false | (any) | (any) | (any) | (any) | PENDING_PARTNER_CREATION | - | true | true | unset | (any) | (any) | present | (falls through) | - | true | true | unset | (any) | (any) | missing | (falls through) | - | true | true | W9 | missing | - | (any) | PENDING_TAX_SUBMISSION | - | true | true | W9 | present | INACTIVE | (any) | PENDING_NEW_TAX_FORM | - | true | true | W9 | present | INVALID_W9_ELECTRONIC_DOCUMENT | (any) | PENDING_NEW_TAX_FORM | - | true | true | W9 | present | INVALID_W9_ELECTRONIC_DOCUMENT_CHECK_INTERNAL | (any) | PENDING_NEW_TAX_FORM | - | true | true | W9 | present | NOT_VERIFIED | (any) | PENDING_TAX_REVIEW | - | true | true | W9 | present | ACTIVE | missing | PENDING_PARTNER_CREATION | - | true | true | W9 | present | ACTIVE | present | (falls through) | - - @motivating @ui - Scenario: MISSING_PAYOUT_CONFIGURATION pendingReason resolves to PENDING_PARTNER_CREATION - Given a reward whose pendingReasons include "MISSING_PAYOUT_CONFIGURATION" - And no earlier rule matches - When the cell is rendered - Then the resulting state is PENDING_PARTNER_CREATION - # ============================================================ - # 10. STATUSES-ARRAY FALLBACK (rules 19–34) - # ============================================================ - - @motivating - Scenario Outline: Single-entry statuses array returns that single value (rule 19) - Given a reward whose pendingReasons array is empty - And reward.partnerFundsTransfer is missing - And reward.statuses is - When the cell is rendered - Then the resulting state is "" - - Examples: - | statuses | state | - | ["AVAILABLE"] | AVAILABLE | - | ["REDEEMED"] | REDEEMED | - | ["EXPIRED"] | EXPIRED | - | ["CANCELLED"] | CANCELLED | - | ["PENDING"] | PENDING | - | ["DENIED"] | DENIED | - | ["UNKNOWN_STATUS"] | UNKNOWN_STATUS | - - @motivating - Scenario Outline: Multi-entry statuses array returns the first matching possibleState (rules 20–34) - Given a reward whose pendingReasons array is empty - And reward.partnerFundsTransfer is missing - And reward.statuses is - When the cell is rendered - Then the resulting state is "" - # possibleStates priority order: - # REDEEMED > CANCELLED > EXPIRED > PENDING > AVAILABLE > PENDING_REVIEW - # > DENIED > PAYOUT_APPROVED > PAYOUT_CANCELLED > PAYOUT_FAILED - # > PENDING_TAX_REVIEW > PENDING_NEW_TAX_FORM > PENDING_TAX_SUBMISSION - # > PENDING_PARTNER_CREATION > PROCESSING - - Examples: - | statuses | state | - | ["AVAILABLE", "PENDING"] | PENDING | - | ["AVAILABLE", "REDEEMED"] | REDEEMED | - | ["EXPIRED", "PENDING"] | EXPIRED | - | ["PENDING", "AVAILABLE"] | PENDING | - | ["AVAILABLE", "DENIED"] | AVAILABLE | - | ["CANCELLED", "AVAILABLE"] | CANCELLED | - | ["EXPIRED", "CANCELLED", "REDEEMED"] | REDEEMED | - | ["PENDING_REVIEW", "DENIED", "AVAILABLE"] | AVAILABLE | - | ["PROCESSING", "PENDING_PARTNER_CREATION"] | PENDING_PARTNER_CREATION | - - @minutia - Scenario: Multi-entry statuses with no matching possibleState returns undefined - Given a reward whose pendingReasons array is empty - And reward.partnerFundsTransfer is missing - And reward.statuses is ["UNKNOWN_A", "UNKNOWN_B"] - When the cell is rendered - Then the resulting state is undefined - And the primary badge text is "Not available" - And the description body has no state-specific status line - - @minutia - Scenario: Empty statuses array with no other matches returns undefined - Given a reward whose pendingReasons array is empty - And reward.partnerFundsTransfer is missing - And reward.statuses is [] - When the cell is rendered - Then the resulting state is undefined - And the primary badge text is "Not available" - # ============================================================ - # 11. ICU CUSTOMIZATION (statusText, statusLongText) - # ============================================================ - - @motivating - Scenario Outline: statusText prop overrides the primary badge text per state - Given the "status-text" prop overrides "" to "" - And a reward whose computed state is "" - And NOT (state is PENDING and reward.dateScheduledFor is set) - When the cell is rendered - Then the primary badge text is exactly "" - - Examples: - | state | customLabel | - | AVAILABLE | Redeem me! | - | PENDING | Coming soon! | - | PAYOUT_APPROVED | Payout Sent! | - | DENIED | Unlucky! | - | PENDING_TAX_REVIEW | Tax review in progress | - | PENDING_PARTNER_CREATION | Finish setup | - - @motivating - Scenario Outline: statusLongText prop overrides the description body per state - Given the "status-long-text" prop overrides "" to "" - And a reward whose computed state is "" - And the conditional render block for "" fires - When the cell is rendered - Then the description body contains "" - - Examples: - | state | customLabel | - | DENIED | Sorry, this referral was denied | - | PAYOUT_FAILED | We were unable to send your money | - | PENDING_TAX_SUBMISSION | Please upload your W-9 form | - | PENDING_PARTNER_CREATION | Finish onboarding | - - @minutia - Scenario: statusLongText for a state without a render block has no effect on the body - Given the "status-long-text" prop overrides "REDEEMED" to "You redeemed!" - And a reward whose computed state is REDEEMED - When the cell is rendered - Then the description body does NOT contain "You redeemed!" - And only the dateGiven and fuelTankCode lines (if any) are rendered - # ============================================================ - # 12. LOCALIZATION - # ============================================================ - - @minutia - Scenario: All dates and ICU strings flow through the configured locale - Given the "locale" prop is set to a supported locale - Then dateGiven, dateExpires, dateCancelled, dateModerated, dateScheduledFor, - And relative time expressions use `DateTime.toRelative()` with the same locale, with the leading "in " stripped - And every prop-driven message string is run through `intl.formatMessage` diff --git a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature b/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature deleted file mode 100644 index fbe54a04b4..0000000000 --- a/packages/mint-components/src/components/sqm-rewards-table/columns/sqm-rewards-table-status-column-new.feature +++ /dev/null @@ -1,240 +0,0 @@ -@author:derek @owner:derek -Feature: Reward Table Status Column - Shows the status of each reward as a coloured pill plus a description - line. The status is derived from the reward's fraud state, the user's - Impact tax/payout connection, the reward's PartnerFundsTransfer (PFT), - the reward's lifecycle dates, and the reward's `statuses` / - `pendingReasons` arrays. - - Background: - Given the status column is included in the reward table - # ============================================================ - # 1. COLUMN CONFIGURATION - # ============================================================ - - @motivating @ui - Scenario Outline: The title of the status column is configurable - Given the "column-title" prop is set to - Then the status column is shown with - - Examples: - | value | columnTitle | - | Status | | - | My column title | My column title | - - @minutia @ui - Scenario Outline: Expiry status date text can be configured - Given the "expiry-text" prop has - And a user with an available reward with an expiry date - When they view the reward table - Then they see under the Available Status pill - - Examples: - | value | text | - | Expires | Expires | - | Redeem before | Redeem before | - # ============================================================ - # 2. STATUS PRECEDENCE - # rewardStatus() walks the rules below from top to bottom and the first - # matching rule wins. - # ============================================================ - - @motivating - Scenario: 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 order: - | order | rule | resulting status | resulting badge text | resulting description | - | 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: PENDING description precedence ladder - Given a PENDING reward, its pendingReasons, and the user's Impact tax connection - Then the description shown under the Pending pill is determined in the following order: - | order | rule | resulting status | resulting badge text | resulting description | - | 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 @ui - Scenario Outline: Payout descriptions for PFT-derived statuses - Given a reward whose computed status is - When the cell is rendered - Then the description line shows - - Examples: - | status | description | - | PAYOUT_APPROVED | Payout approved and scheduled for payment 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 . Payout is then scheduled based on your settings. | - - @motivating @ui - Scenario Outline: US_TAX pending reason resolves based on the tax connection state - Given the user has a PENDING reward whose pendingReasons include "US_TAX" - And the user's impactConnection.taxHandlingEnabled is - And the user's impactConnection.connected is - And the user's publisher.requiredTaxDocumentType is - And the user's publisher.currentTaxDocument is - And the user's publisher.currentTaxDocument.status is - And the user's publisher.withdrawalSettings is - Then the badge is shown in a pill with text - And the description line shows - - Examples: - | taxHandlingEnabled | connected | requiredTaxDocumentType | currentTaxDocument | currentTaxDocumentStatus | withdrawalSettings | badgeText | pillColour | description | - | false | true | W9 | present | ACTIVE | present | Pending | warning | W-9 required | - | true | false | - | - | - | - | Pending | warning | Complete your tax and cash payout setup to receive your rewards. | - | true | true | W9 | missing | - | present | Pending | warning | Submit your tax documents to receive your rewards. | - | true | true | W9 | present | INACTIVE | present | Pending | warning | Invalid tax form. Submit a new form to receive your rewards. | - | true | true | W9 | present | NOT_VERIFIED | present | Pending | warning | Awaiting tax form review. | - | true | true | W9 | present | ACTIVE | missing | Pending | warning | Complete your tax and cash payout setup to receive your rewards. | - # ============================================================ - # 3. STATUS → BADGE COLOUR MAPPING - # ============================================================ - - @motivating @ui - Scenario Outline: Each status maps to a badge colour and default text - Given a reward whose computed status is - When the cell is rendered - Then the badge is shown in a pill with - - Examples: - | status | text | pillColour | - | AVAILABLE | Available | success | - | REDEEMED | Redeemed | primary | - | PAYOUT_APPROVED | Payout Approved | primary | - | PENDING | Pending | warning | - | PENDING_REVIEW | Pending | warning | - | PROCESSING | Payment Processing | warning | - | CANCELLED | Cancelled | danger | - | EXPIRED | Expired | danger | - | DENIED | Denied | danger | - | PAYOUT_FAILED | Payout Failed | danger | - | PAYOUT_CANCELLED | Payout Cancelled | danger | - | (other / "") | Not available | danger | - # ============================================================ - # 4. DESCRIPTION LINE PRECEDENCE - # The description under the badge is selected in this order: - # 1. Fraud descriptions (PENDING_REVIEW / DENIED) - # 2. Pending-reason descriptions (only when status is PENDING) - # 3. Payout descriptions (PAYOUT_APPROVED / PAYOUT_FAILED / - # PAYOUT_CANCELLED / PROCESSING) - # 4. Reward date (dateCancelled / dateExpires / dateRedeemed), - # prefixed with the configured expiry-text when status is AVAILABLE - # ============================================================ - - @motivating @ui - Scenario Outline: Fraud descriptions are shown for fraud-derived statuses - Given a reward whose computed status is - When the cell is rendered - Then the description line shows - - Examples: - | status | description | - | DENIED | Detected self-referral | - | PENDING_REVIEW | Awaiting review | - - @motivating @ui - Scenario Outline: Date-based descriptions for terminal lifecycle states - Given a reward whose computed status is - And the reward has populated - When the cell is rendered - Then the description line shows - - Examples: - | status | date | text | - | AVAILABLE | dateExpires | Expires | - | EXPIRED | dateExpires | | - | CANCELLED | dateCancelled | | - | REDEEMED | dateRedeemed | | - # ============================================================ - # 5. PENDING DESCRIPTION PRECEDENCE - # Once the status precedence ladder in § 2 has produced PENDING, the - # description shown under the "Pending" pill is determined by the - # following ordered ladder (mirrors getTaxPendingReasons + the - # pendingCodeMap fallback). All rules render a warning pill with text - # "Pending"; only the description varies. - # ============================================================ - - @motivating @ui - Scenario Outline: Generic pendingReasons codes are mapped via pendingCodeMap - Given the user has a PENDING reward - And the reward has no tax/payout pending reason - And the reward.pendingReasons include - When the cell is rendered - Then the description line shows - - Examples: - | pendingReason | text | - | SCHEDULED | Until | - | UNHANDLED_ERROR | Fulfillment error | - | SUSPECTED_FRAUD | Awaiting review | - - @minutia - Scenario: Multiple pending reasons are joined with commas - Given the user has a PENDING reward - And the reward has no tax/payout pending reason - And the reward.pendingReasons include both "UNHANDLED_ERROR" and "SCHEDULED" - Then the description line shows the mapped strings joined by ", " - - @minutia - Scenario: Unknown pending reason codes pass through verbatim - Given the user has a PENDING reward - And the reward.pendingReasons include a code not present in the pendingCodeMap - Then the raw code is shown in the description line - # ============================================================ - # 6. STATUS TEXT CUSTOMIZATION (status-text prop) - # ============================================================ - - @motivating - Scenario Outline: Statuses can be customized via ICU format - Given the "status-text" prop is "{status, select, AVAILABLE {Redeem me!} CANCELLED {Unavailable} PENDING {Coming soon!} EXPIRED {Past due} REDEEMED {Spent} PENDING_REVIEW {Pending Review!} PAYOUT_APPROVED {Payout Sent!} PAYOUT_FAILED {Payout Failed!} PAYOUT_CANCELLED {Payout Cancelled!} PROCESSING {Processing!} DENIED {Unlucky!}}" - And a reward whose computed status is - When the cell is rendered - Then the badge is rendered in a pill with text - - Examples: - | status | text | pillColour | - | AVAILABLE | Redeem me! | success | - | CANCELLED | Unavailable | danger | - | PENDING | Coming soon! | warning | - | EXPIRED | Past due | danger | - | REDEEMED | Spent | primary | - | PENDING_REVIEW | Pending Review! | warning | - | PAYOUT_APPROVED | Payout Sent! | primary | - | PAYOUT_FAILED | Payout Failed! | danger | - | PAYOUT_CANCELLED | Payout Cancelled! | danger | - | PROCESSING | Processing! | warning | - | DENIED | Unlucky! | danger | - # ============================================================ - # 7. LOCALIZATION - # ============================================================ - - @minutia - Scenario: Dates are formatted in the user's locale - Given the "locale" prop is set to a supported locale - Then every placeholder above is formatted with that locale - And the configurable text props (expiryText, payoutProcessing, etc.) flow through `intl.formatMessage` From 92dab7fa47e9ad78df36c69ac301afb2ec6a5c4e Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 21 May 2026 15:04:38 -0700 Subject: [PATCH 31/64] Add support ICU string in partner modal --- packages/mint-components/package.json | 2 +- packages/mint-components/src/components.d.ts | 10 +++++++ .../PartnerInfoModal.stories.tsx | 26 +++++++++++++++++++ .../sqm-partner-info-modal/readme.md | 5 +++- .../sqm-partner-info-modal-view.tsx | 17 +++++++++++- .../sqm-partner-info-modal.tsx | 11 +++++++- .../src/components/sqm-stencilbook/readme.md | 4 ++- 7 files changed, 70 insertions(+), 5 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index c3d7aa4edb..bed5330040 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-2", + "version": "2.2.0-3", "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", diff --git a/packages/mint-components/src/components.d.ts b/packages/mint-components/src/components.d.ts index e23a0a9f66..a9d41352ec 100644 --- a/packages/mint-components/src/components.d.ts +++ b/packages/mint-components/src/components.d.ts @@ -2398,6 +2398,11 @@ export namespace Components { * @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 @@ -10600,6 +10605,11 @@ declare namespace LocalJSX { * @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 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 index 1c6f43c69f..7d32e8ac2a 100644 --- 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 @@ -45,6 +45,7 @@ const defaultText = { "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", @@ -182,3 +183,28 @@ export const Closed = () => { }; 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 index be3910479f..da55359694 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/readme.md +++ b/packages/mint-components/src/components/sqm-partner-info-modal/readme.md @@ -25,7 +25,8 @@ | `searchCurrencyPlaceholder` | `search-currency-placeholder` | | `string` | `"Search for a currency"` | | `stateController` | `state-controller` | | `string` | `"{}"` | | `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 Support or sign up for this referral program with a different email."` | +| `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"` | @@ -34,6 +35,7 @@ ### Used by + - [sqm-stencilbook](../sqm-stencilbook) - [sqm-widget-verification](../sqm-widget-verification) ### Depends on @@ -45,6 +47,7 @@ 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 ``` 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 index ed53a6b85f..44a8d18943 100644 --- 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 @@ -37,6 +37,7 @@ export interface PartnerInfoModalViewProps { searchCountryPlaceholder: string; searchCurrencyPlaceholder: string; supportDescriptionExistingPartner: string; + supportLink: string; modalHeaderExistingPartner: string; allowBankingCollection: string; termsAndConditionsLabel: string; @@ -113,10 +114,24 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) { 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}

-

{text.supportDescriptionExistingPartner}

+

{supportMessage}

) : (

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 index 898887990f..c130e9979f 100644 --- 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 @@ -72,7 +72,15 @@ export class PartnerInfoModal { */ @Prop() supportDescriptionExistingPartner: string = - "If this is a mistake, please contact Support or sign up for this referral program with a different email."; + "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}. @@ -250,6 +258,7 @@ function useDemoPartnerInfoModal( searchCurrencyPlaceholder: props.searchCurrencyPlaceholder, supportDescriptionExistingPartner: props.supportDescriptionExistingPartner, + supportLink: props.supportLink, modalHeaderExistingPartner: props.modalHeaderExistingPartner, }, }, diff --git a/packages/mint-components/src/components/sqm-stencilbook/readme.md b/packages/mint-components/src/components/sqm-stencilbook/readme.md index f28bfb36e6..77969aaccf 100644 --- a/packages/mint-components/src/components/sqm-stencilbook/readme.md +++ b/packages/mint-components/src/components/sqm-stencilbook/readme.md @@ -104,6 +104,7 @@ - [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) ### Graph @@ -206,6 +207,7 @@ 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-form-message --> sqm-skeleton sqm-portal-register --> sqm-form-message @@ -285,10 +287,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-partner-info-modal --> sqm-form-message style sqm-stencilbook fill:#f9f,stroke:#333,stroke-width:4px ``` From fdf4354481804a86a6e8e10f01e380646a9fa436 Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 22 May 2026 16:41:19 -0700 Subject: [PATCH 32/64] Re-add removed code to fix code verification not showing after email was sent --- packages/mint-components/package.json | 2 +- .../useEmailVerification.ts | 35 +++-- .../useWidgetVerification.ts | 7 + .../sqm-user-info-form.feature | 140 +++++++++++++++++- 4 files changed, 165 insertions(+), 19 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index bed5330040..b576fb64cd 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-3", + "version": "2.2.0-4", "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", 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..89dfd0dc06 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 @@ -87,8 +87,8 @@ export function useUpsertUserEmail() { export function useWidgetEmailVerification( 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,40 @@ 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/useWidgetVerification.ts b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts index 1684880e69..809784bb72 100644 --- a/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts +++ b/packages/mint-components/src/components/sqm-widget-verification/useWidgetVerification.ts @@ -10,6 +10,7 @@ import { PARTNER_CREATED_NAMESPACE, SHOW_CODE_NAMESPACE, SHOW_PARTNER_MODAL_NAMESPACE, + VERIFICATION_EMAIL_NAMESPACE, VERIFICATION_PARENT_NAMESPACE, } from "./keys"; @@ -38,6 +39,12 @@ export function useWidgetVerification() { namespace: SHOW_CODE_NAMESPACE, initialValue: false, }); + + useParentState({ + namespace: VERIFICATION_EMAIL_NAMESPACE, + initialValue: undefined, + }); + const setContext = useSetParent(VERIFICATION_PARENT_NAMESPACE); const [loading, setLoading] = useState(true); const [showPartnerModal, setShowPartnerModal] = useParentState({ diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature index 82fe0d3328..d5c8f1e7a4 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature @@ -13,6 +13,7 @@ Feature: Tax Form Step One And each field has label

+ + + +
+ ); +}; \ No newline at end of file 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..c99cf6044b 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 ( 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/templates/TaxAndCash.html b/packages/mint-components/src/templates/TaxAndCash.html index fc628eee15..5993763a4a 100644 --- a/packages/mint-components/src/templates/TaxAndCash.html +++ b/packages/mint-components/src/templates/TaxAndCash.html @@ -305,7 +305,7 @@

Activity

dashboard_not-registered-for-tax="Not registered for indirect tax. If you’ve previously registered with your tax authority, contact Support to add your information to stay tax compliant." dashboard_qst-number="QST number: {qstNumber}" dashboard_sub-region-tax-number="Income tax number: {subRegionTaxNumber}" - dashboard_status-badge-text="{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} }" + dashboard_status-badge-text="{badgeText, select, payoutToday {Payout Today} nextPayout {Next Payout} other {Failed} }" dashboard_threshold-payout-text="Next payout occurs when balance is {thresholdBalance}" dashboard_account-text="Account" dashboard_invoice-column-title="Invoice" From 92db0e780a65a56847b2da2f7d0f02076d80c161 Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 10 Jun 2026 10:15:47 -0700 Subject: [PATCH 51/64] Update countries order in partner modal to be US first then sorted alphabetically after --- .../usePartnerInfoModal.tsx | 43 +++++-------------- 1 file changed, 10 insertions(+), 33 deletions(-) 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 index 2c93fde786..b3cb9f6016 100644 --- a/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx +++ b/packages/mint-components/src/components/sqm-partner-info-modal/usePartnerInfoModal.tsx @@ -63,33 +63,6 @@ export const GET_CURRENCIES = gql` } `; -export const CONNECT_PARTNER = gql` - mutation createImpactConnection($vars: ImpactConnectionInput!) { - createImpactConnection(impactConnectionInput: $vars) { - success - validationErrors { - field - message - } - user { - id - accountId - impactConnection { - connected - publisher { - brandedSignup - requiredTaxDocumentType - currentTaxDocument { - type - status - } - } - } - } - } - } -`; - const START_IMPACT_CONNECTION = gql` mutation startImpactConnection($vars: ImpactConnectionInput!) { startImpactConnection(impactConnectionInput: $vars) { @@ -174,7 +147,15 @@ export function usePartnerInfoModal( const [allowBankingCollection, setAllowBankingCollection] = useState(false); - const countries = countriesData?.impactPayoutCountries?.data || []; + 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; @@ -211,9 +192,7 @@ export function usePartnerInfoModal( const [countrySearch, setCountrySearch] = useState(""); const [currencySearch, setCurrencySearch] = useState(""); - const [filteredCountries, setFilteredCountries] = useState( - countriesData?.impactPayoutCountries?.data || [] - ); + const [filteredCountries, setFilteredCountries] = useState(countries || []); const [filteredCurrencies, setFilteredCurrencies] = useState( currencies || [] ); @@ -337,8 +316,6 @@ export function usePartnerInfoModal( } } - console.log(impactConnection, "impactConnection in partner modal"); - const showModal = !success && !userLoading && From 8bf7c8eed0df6672ea90daeb3b2378b614f0b641 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:38:44 +0000 Subject: [PATCH 52/64] publish: @saasquatch/mint-components v2.3.0-7 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index f45e27ea25..f068fd27d7 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-6", + "version": "2.3.0-7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-6", + "version": "2.3.0-7", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index c7b2ee7a7f..dcf5a620af 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.3.0-6", + "version": "2.3.0-7", "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", From 1a6bd6eee13b78885d7b58e3fb2baa838cd49a4b Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 10 Jun 2026 15:21:16 -0700 Subject: [PATCH 53/64] Add phone number validation that mirrors Impact APIs validation to user info form. --- packages/mint-components/package.json | 2 +- .../sqm-user-info-form-view.tsx | 67 ++++++++++++------- .../sqm-user-info-form/useUserInfoForm.tsx | 62 +++++++++-------- .../src/components/tax-and-cash/utils.ts | 61 +++++++++++++++++ 4 files changed, 139 insertions(+), 53 deletions(-) diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index c7b2ee7a7f..dcf5a620af 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.3.0-6", + "version": "2.3.0-7", "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", 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 a5618bff0a..631fedbae5 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 @@ -4,7 +4,11 @@ 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: { @@ -412,7 +416,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.termsAndConditionsLabel} ), - }, + } ); let regionLabel = undefined; @@ -438,6 +442,17 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { ) { 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]; } @@ -475,7 +490,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { {text.supportLink} ), - }, + } )}

@@ -495,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 } )}

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

@@ -543,7 +558,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.firstName, - formState.errors.firstName, + formState.errors.firstName ), } : {})} @@ -561,7 +576,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.lastName, - formState.errors.lastName, + formState.errors.lastName ), } : {})} @@ -593,7 +608,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.country, - formState.errors.countryCode, + formState.errors.countryCode ), } : {})} @@ -687,13 +702,19 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { id="phoneNumber" name="/phoneNumber" value={formState.phoneNumber} - validationError={({ value }) => { - // Naive phone number validation - validateBillingField(/[a-zA-Z]+/, value) && - formatErrorMessage( - text.phoneNumber, - text.error.fieldInvalidError, - ); + 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") @@ -703,7 +724,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.phoneNumber, - formState.errors.phoneNumber, + formState.errors.phoneNumber ), } : {})} @@ -722,7 +743,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.address, - text.error.invalidCharacterError, + text.error.invalidCharacterError ) } disabled={ @@ -733,7 +754,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.address, - formState.errors.address, + formState.errors.address ), } : {})} @@ -750,7 +771,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { !validateBillingField(/^[\x20-\xFF]+$/, value) && formatErrorMessage( text.city, - text.error.invalidCharacterError, + text.error.invalidCharacterError ) } disabled={ @@ -761,7 +782,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.city, - formState.errors.city, + formState.errors.city ), } : {})} @@ -782,7 +803,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.state, - formState.errors.state, + formState.errors.state ), } : {})} @@ -807,7 +828,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.postalCode, - formState.errors.postalCode, + formState.errors.postalCode ), } : {})} @@ -826,7 +847,7 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { class: classes.ErrorInput, helpText: formatErrorMessage( text.currency, - formState.errors.currency, + formState.errors.currency ), } : {})} diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index 81219e68ee..acd83c4351 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -53,6 +53,7 @@ export type ValidationErrorFunction = (input: { control; key: string; value; + formData?: FormState; }) => string | undefined; export type FormState = { @@ -86,7 +87,7 @@ export function useUserInfoForm(props: TaxForm) { const countries = useParentValue(SORTED_COUNTRIES_NAMESPACE); const [step, setStep] = useParent(TAX_CONTEXT_NAMESPACE); const [userFormContext, setUserFormContext] = useParent( - USER_FORM_CONTEXT_NAMESPACE, + USER_FORM_CONTEXT_NAMESPACE ); const user = useUserIdentity(); @@ -110,20 +111,20 @@ export function useUserInfoForm(props: TaxForm) { const currencies = useMemo( () => [...(_currencies || [])].sort((a, b) => - a.displayName.localeCompare(b.displayName), + a.displayName.localeCompare(b.displayName) ), - [_currencies], + [_currencies] ); const [countrySearch, setCountrySearch] = useState(""); const [phoneCountrySearch, setPhoneCountrySearch] = useState(""); const [filteredCountries, setFilteredCountries] = useState(countries || []); const [filteredPhoneCountries, setFilteredPhoneCountries] = useState( - countries || [], + countries || [] ); const [currencySearch, setCurrencySearch] = useState(""); const [filteredCurrencies, setFilteredCurrencies] = useState( - currencies || [], + currencies || [] ); const [formErrors, setErrors] = useState({}); @@ -206,8 +207,8 @@ export function useUserInfoForm(props: TaxForm) { } else { setFilteredCountries( countries.filter((c) => - c.displayName.toLowerCase().includes(countrySearch.toLowerCase()), - ) || [], + c.displayName.toLowerCase().includes(countrySearch.toLowerCase()) + ) || [] ); } }, [countrySearch, countries]); @@ -219,10 +220,8 @@ export function useUserInfoForm(props: TaxForm) { } else { setFilteredPhoneCountries( countries.filter((c) => - c.displayName - .toLowerCase() - .includes(phoneCountrySearch.toLowerCase()), - ) || [], + c.displayName.toLowerCase().includes(phoneCountrySearch.toLowerCase()) + ) || [] ); } }, [phoneCountrySearch, countries]); @@ -234,8 +233,8 @@ export function useUserInfoForm(props: TaxForm) { } else { setFilteredCurrencies( currencies.filter((c) => - c.currencyCode.toLowerCase().includes(currencySearch.toLowerCase()), - ) || [], + c.currencyCode.toLowerCase().includes(currencySearch.toLowerCase()) + ) || [] ); } }, [currencySearch, currencies]); @@ -263,25 +262,30 @@ export function useUserInfoForm(props: TaxForm) { const userData = data?.user; let result = null; let connectionResult; - if (userData?.impactConnection?.connectionStatus === "STARTED") { - result = await completeImpactPartner({ - vars, - }); - connectionResult = (result as CompletePartnerResult) - ?.completeImpactConnection; - } else { - result = await connectImpactPartner({ - vars, - }); - connectionResult = (result as ConnectPartnerResult) - ?.createImpactConnection; - } + result = await completeImpactPartner({ + vars, + }); + connectionResult = (result as CompletePartnerResult) + ?.completeImpactConnection; + // if (userData?.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 (!connectionResult?.success) { console.error( "Failed to create Impact connection: ", - connectionResult?.validationErrors, + connectionResult?.validationErrors ); throw new Error(); @@ -319,7 +323,7 @@ export function useUserInfoForm(props: TaxForm) { // custom validation if (typeof control.validationError === "function") { const validate = control.validationError as ValidationErrorFunction; - const validationError = validate({ control, key, value }); + const validationError = validate({ control, key, value, formData }); if (validationError) jsonpointer.set(errors, key, validationError); } @@ -400,7 +404,7 @@ export function useUserInfoForm(props: TaxForm) { } const hasStates = ["ES", "AU", "US", "CA"].includes( - userFormContext.countryCode, + userFormContext.countryCode ); const regionObj = hasStates ? ADDRESS_REGIONS[userFormContext?.countryCode] diff --git a/packages/mint-components/src/components/tax-and-cash/utils.ts b/packages/mint-components/src/components/tax-and-cash/utils.ts index 6064fcf003..2e2bc3eaa8 100644 --- a/packages/mint-components/src/components/tax-and-cash/utils.ts +++ b/packages/mint-components/src/components/tax-and-cash/utils.ts @@ -1,5 +1,66 @@ import { intl } from "../../global/global"; import { TaxDocumentType } from "./data"; +import { PHONE_EXTENSIONS } from "./phoneExtensions"; + +function stripLeadingZero(d: string) { + return d.startsWith("0") ? d.slice(1) : d; +} + +/** + * Mirrors Impact's I18nPhoneNumberParams validation rules per country. + * Returns true when the phone number is valid for the given the phoneNumberCountryCode country. + */ +export function isValidI18nPhoneNumber( + phoneCountryCode: string | undefined, + phoneNumber: string | undefined +): boolean { + + if (!phoneCountryCode || !phoneNumber?.trim()) return false; + + const country = phoneCountryCode.toUpperCase(); + const digits = phoneNumber.replace(/\D/g, ""); + + switch (country) { + case "US": + case "CA": { + // strip a single leading "1", then exactly 10 digits + const n = digits.startsWith("1") ? digits.slice(1) : digits; + return n.length === 10; + } + case "AU": + // 9 or 10 digits + return digits.length === 9 || digits.length === 10; + case "NZ": { + // trim "640" country prefix or leading "0" (only when >9 digits), + // then 8–10 digits + let n = digits; + if (n.length > 9) { + if (n.startsWith("640")) n = n.slice(3); + else if (n.startsWith("0")) n = n.slice(1); + } + return n.length >= 8 && n.length <= 10; + } + case "GB": + // >= 6 digits after stripping a single leading "0", no dialing-code check + return stripLeadingZero(digits).length >= 6; + default: { + // All other countries: strip a single leading "0", then >= 6 digits + if (stripLeadingZero(digits).length < 6) return false; + // If input starts with "+", the dialing code (first whitespace-split + // token) must match the selected country exactly. Splits on first + // whitespace, so "+4420 7946 0958" supplies "4420" (not "44") and fails. + if (phoneNumber.trim().startsWith("+")) { + const supplied = phoneNumber.trim().split(/\s/)[0].slice(1); + const expected = PHONE_EXTENSIONS[country]?.dial_code?.replace( + /^\+/, + "" + ); + return expected === supplied; + } + return true; + } + } +} export function validTaxDocument(requiredType: TaxDocumentType | undefined) { const validTypes = ["W9", "W8BENE", "W8BEN"]; From d116359de3021e562a80f3dd4b79bf5549f426a6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 22:32:29 +0000 Subject: [PATCH 54/64] publish: @saasquatch/mint-components v2.3.0-8 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index f068fd27d7..cacfe2d79b 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-7", + "version": "2.3.0-8", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-7", + "version": "2.3.0-8", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index dcf5a620af..4c640d7df8 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.3.0-7", + "version": "2.3.0-8", "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", From 32a8b0ad097f8a7a9c31a25038ba0da68b58ed39 Mon Sep 17 00:00:00 2001 From: Sam Beveridge Date: Wed, 10 Jun 2026 16:03:08 -0700 Subject: [PATCH 55/64] fix issue where dialogs were stealing input focus on change --- .../sqm-partner-info-modal/sqm-partner-info-modal-view.tsx | 2 ++ .../sqm-partner-info-modal/sqm-partner-info-modal.tsx | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) 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 index 36c193ae25..6b4efe5032 100644 --- 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 @@ -25,6 +25,7 @@ export interface PartnerInfoModalViewProps { setCountrySearch: (c: any) => void; onSubmit: () => void; onClose: () => void; + onInitialFocus?: (e: any) => void; }; text: { modalHeader: string; @@ -292,6 +293,7 @@ export function PartnerInfoModalView(props: PartnerInfoModalViewProps) { e.preventDefault(); } }} + onSl-initial-focus={callbacks.onInitialFocus} >

{states.isExistingPartner 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 index 10f7cfe348..96a266f67e 100644 --- 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 @@ -195,7 +195,7 @@ export class PartnerInfoModal { } function useDemoPartnerInfoModal( - props: PartnerInfoModal + props: PartnerInfoModal, ): PartnerInfoModalViewProps { const [countryCode, setCountryCode] = useState("US"); const [currency, setCurrency] = useState(""); @@ -247,6 +247,7 @@ function useDemoPartnerInfoModal( setError(""); }, onClose: () => {}, + onInitialFocus: (e: any) => e.preventDefault(), }, text: { modalHeader: props.modalHeader, @@ -265,6 +266,6 @@ function useDemoPartnerInfoModal( }, }, props.demoData || stateOverride, - { arrayMerge: (_, a) => a } + { arrayMerge: (_, a) => a }, ); } From 40641a3fa6e7e080a0efae6df3564836674119fc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 23:06:13 +0000 Subject: [PATCH 56/64] publish: @saasquatch/mint-components v2.3.0-9 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index cacfe2d79b..2b8a3dbf8d 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-8", + "version": "2.3.0-9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-8", + "version": "2.3.0-9", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 4c640d7df8..61c02cfade 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.3.0-8", + "version": "2.3.0-9", "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", From f38f042f862ff8c8e9b661b0e8bbcff108e282fc Mon Sep 17 00:00:00 2001 From: Andy Date: Wed, 10 Jun 2026 17:53:55 -0700 Subject: [PATCH 57/64] Add specs for phone number validation --- .../sqm-user-info-form.feature | 155 ++++++++++++++++++ .../sqm-user-info-form/useUserInfoForm.tsx | 1 + 2 files changed, 156 insertions(+) diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature index d6a905282f..1ce8a34434 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature @@ -371,3 +371,158 @@ Feature: Tax Form Step One Then the "Phone number" field is empty and enabled And the "Extension" (phoneNumberCountryCode) field is empty and enabled And the user must provide a real phone number before "Continue" will succeed + + @motivating + Scenario: Phone number is validated against the selected country's rules + # Validation mirrors Impact's I18nPhoneNumberParams rules so the partner upsert + # request is not rejected by the backend after the user advances past step 1. + Given the "Phone number" field is not empty + And the "Extension" (phoneNumberCountryCode) field has a value selected + When the user clicks "Continue" + Then the phone number is validated against the rules of the selected country + And if it is invalid the error message "Phone number is invalid." is displayed under the "Phone number" field + And no save request is sent to the backend + And they are not sent to the next step + + @motivating + Scenario: Empty phone number is handled by the required validator, not the phone-format validator + Given the "Phone number" field is empty or whitespace-only + When the user clicks "Continue" + Then the error message "Phone number is required" is displayed + And the "Phone number is invalid." error is NOT displayed + + @motivating + Scenario Outline: Phone number validation per country (US / CA) + Given the "Extension" (phoneNumberCountryCode) field has value + And the "Phone number" field has value + When the user clicks "Continue" + Then non-digit characters in the phone number are ignored + And a single leading "1" country-code digit is ignored + And the phone number is valid only when the digit count is exactly 10 + And the phone validation result + + Examples: + | countryCode | phoneNumber | isValid | + | US | 4155551234 | passes | + | US | (415) 555-1234 | passes | + | US | 415-555-1234 | passes | + | US | 14155551234 | passes | + | CA | 6045551234 | passes | + | CA | 1-604-555-1234 | passes | + | US | 415555123 | fails | + | US | 41555512345 | fails | + | US | abc | fails | + | CA | 604555123 | fails | + + @motivating + Scenario Outline: Phone number validation per country (AU) + Given the "Extension" (phoneNumberCountryCode) field has value "AU" + And the "Phone number" field has value + When the user clicks "Continue" + Then non-digit characters in the phone number are ignored + And no country-code stripping is performed + And the phone number is valid only when the digit count is exactly 9 or 10 + And the phone validation result + + Examples: + | phoneNumber | isValid | + | 412345678 | passes | + | 0412345678 | passes | + | 04 1234 5678 | passes | + | 12345678 | fails | + | 04123456789 | fails | + + @motivating + Scenario Outline: Phone number validation per country (NZ) + Given the "Extension" (phoneNumberCountryCode) field has value "NZ" + And the "Phone number" field has value + When the user clicks "Continue" + Then non-digit characters in the phone number are ignored + And if the digit count is greater than 9 a leading "640" or a single leading "0" is stripped + And the phone number is valid only when the remaining digit count is between 8 and 10 + And the phone validation result + + Examples: + | phoneNumber | isValid | + | 21234567 | passes | + | 212345678 | passes | + | 0212345678 | passes | + | 64021234567 | passes | + | 1234567 | fails | + | 02123456789 | fails | + + @motivating + Scenario Outline: Phone number validation per country (GB) + Given the "Extension" (phoneNumberCountryCode) field has value "GB" + And the "Phone number" field has value + When the user clicks "Continue" + Then non-digit characters in the phone number are ignored + And a single leading "0" is stripped + And the phone number is valid only when the remaining digit count is at least 6 + And no dialing-code (+44) match is enforced + And the phone validation result + + Examples: + | phoneNumber | isValid | + | 2079460958 | passes | + | 02079460958 | passes | + | 020 7946 0958 | passes | + | 12345 | fails | + | 012345 | fails | + + @motivating + Scenario Outline: Phone number validation for all other countries + Given the "Extension" (phoneNumberCountryCode) field has value + And the "Phone number" field has value + When the user clicks "Continue" + Then non-digit characters in the phone number are ignored + And a single leading "0" is stripped + And the phone number is valid only when the remaining digit count is at least 6 + And if the raw input begins with "+" the first whitespace-delimited token after the "+" must exactly equal the selected country's dial code + And the phone validation result + + Examples: + | countryCode | phoneNumber | isValid | note | + | DE | 30123456 | passes | digits only, length >= 6 | + | DE | 030 12 34 56 | passes | leading "0" stripped | + | DE | +49 30 12 34 56 | passes | "+49" matches DE dial code | + | FR | +33 1 42 68 53 00 | passes | "+33" matches FR dial code | + | DE | +44 30 12 34 56 | fails | "+44" does not match DE dial code | + | FR | 12345 | fails | length < 6 after stripping leading "0" | + | ES | +3491 123 4567 | fails | "+3491" is not the ES dial code (must be "+34") | + + @motivating + Scenario: Phone validation always runs on submit if editable + Given the "Phone number" and "Phone Number Country" fields are not disabled + When the user clicks "Continue" + Then phone validation runs as part of form submission + And if validation fails no save request is sent to the backend + And they are not sent to the next step + + @motivating + Scenario: Phone validation is skipped for disabled fields + Given the user is an Impact partner + And the prefilled "Phone number" field is valid for the prefilled "Phone Number Country" + Then the "Phone number" and "Phone Number Country" fields are disabled + When the user clicks "Continue" + Then phone validation is not run for the disabled fields + And submission proceeds + + @motivating + Scenario Outline: Phone number is sanitized to a domestic number before being sent to Impact + Given the "Phone Number Country" (phoneNumberCountryCode) field has value + And the "Phone number" field has value + When the user clicks "Continue" + And the form passes validation + Then the value submitted as phoneNumber is + And the value submitted as phoneNumberCountryCode is + + Examples: + | countryCode | input | submitted | + | US | +1 415 555 1234 | 415 555 1234 | + | US | 0014155551234 | 4155551234 | + | US | 4155551234 | 4155551234 | + | GB | +44 20 7946 0958 | 20 7946 0958 | + | GB | 020 7946 0958 | 020 7946 0958 | + | DE | +49 30 12 34 56 | 30 12 34 56 | + | DE | 030 12 34 56 | 030 12 34 56 | diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index acd83c4351..a71c1f6dc4 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -393,6 +393,7 @@ export function useUserInfoForm(props: TaxForm) { const indirectTaxCountry = tenantData?.tenantSettings?.impactBrandIndirectTaxCountryCode; + const hasIndirectTax = !!indirectTaxCountry; function getSkipNextStep(userData) { From 331495972887f5bd3e5e82a1b7aced50a1d396c6 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 11 Jun 2026 09:46:04 -0700 Subject: [PATCH 58/64] Remove isPartner from indirect tax form view to undisable fields --- .../sqm-indirect-tax-form/sqm-indirect-tax-form-view.tsx | 8 ++++---- .../sqm-indirect-tax-form/sqm-indirect-tax-form.tsx | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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..f962558390 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 @@ -271,7 +271,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { )} )} - {states.isPartner && ( + {/* {states.isPartner && (

{text.isPartnerAlertHeader}

{intl.formatMessage( @@ -291,7 +291,7 @@ export const IndirectTaxFormView = (props: IndirectTaxFormViewProps) => { } )}
- )} + )} */}

{text.indirectTaxDetails}

@@ -309,7 +309,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 +325,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} 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..8018920c13 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 { Date: Thu, 11 Jun 2026 16:50:45 +0000 Subject: [PATCH 59/64] publish: @saasquatch/mint-components v2.3.0-10 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index 2b8a3dbf8d..87c448f489 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-9", + "version": "2.3.0-10", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-9", + "version": "2.3.0-10", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 61c02cfade..8871019cbd 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.3.0-9", + "version": "2.3.0-10", "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", From d0445f8e915dd1bbb1777d0fb004a0926af6bfd5 Mon Sep 17 00:00:00 2001 From: Andy Date: Thu, 11 Jun 2026 13:14:41 -0700 Subject: [PATCH 60/64] Add validation for phone number before completeImpactPartner runs for existing partner case. Correct TaxAndCash ICU string in template. Remove disabled check for partner in indirectTaxForm --- .../useIndirectTaxForm.tsx | 18 ++++++++++-------- .../sqm-tax-and-cash/useTaxAndCash.tsx | 12 ++++++++---- .../small-views/IndirectTaxDetailsView.tsx | 4 ++-- .../src/templates/TaxAndCash.html | 4 ++-- 4 files changed, 22 insertions(+), 16 deletions(-) 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 1051fd2e2e..92c713230a 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 @@ -176,9 +176,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); @@ -203,7 +203,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { const _option = getOption( _countries, - publisher.taxInformation.indirectTaxCountryCode, + publisher.taxInformation.indirectTaxCountryCode ); setOption(_option); }, [publisher, _countries]); @@ -214,8 +214,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]); @@ -281,6 +281,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { // 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") { @@ -302,7 +303,7 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { // Output backend errors to console for now console.error( "Failed to create Impact connection: ", - connectionResult?.validationErrors, + connectionResult?.validationErrors ); throw new Error(); @@ -356,8 +357,9 @@ export function useIndirectTaxForm(props: IndirectTaxForm) { setLoading(true); try { - const { resultPublisher, hasValidCurrentDocument } = - await connectPartner(formData); + const { resultPublisher, hasValidCurrentDocument } = await connectPartner( + formData + ); if ( resultPublisher?.requiredTaxDocumentType && 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 06eba12187..a895da3f90 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 @@ -8,7 +8,7 @@ import { 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, @@ -54,7 +54,6 @@ function getCurrentStep(user: UserQuery["user"]) { billingCity, billingCountryCode, billingPostalCode, - billingState, phoneNumber, phoneNumberCountryCode, } = user.impactConnection.publisher; @@ -65,7 +64,8 @@ function getCurrentStep(user: UserQuery["user"]) { billingCountryCode && billingPostalCode && phoneNumberCountryCode && - phoneNumber; + phoneNumber && + isValidI18nPhoneNumber(phoneNumberCountryCode, phoneNumber); if (!hasBillingInfo) { return "/1"; @@ -268,7 +268,11 @@ export function useTaxAndCash() { publisher.billingCountryCode && publisher.billingPostalCode && publisher.phoneNumberCountryCode && - publisher.phoneNumber; + publisher.phoneNumber && + isValidI18nPhoneNumber( + publisher.phoneNumberCountryCode, + publisher.phoneNumber + ); if ( hasBillingInfo && 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..782735ac11 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 @@ -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} @@ -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} diff --git a/packages/mint-components/src/templates/TaxAndCash.html b/packages/mint-components/src/templates/TaxAndCash.html index 5993763a4a..fc9b3dafd3 100644 --- a/packages/mint-components/src/templates/TaxAndCash.html +++ b/packages/mint-components/src/templates/TaxAndCash.html @@ -208,9 +208,9 @@

Activity

step-2_not-registered="Not registered for indirect tax" step-2_not-registered-subtext="If you’ve never set up indirect tax with your tax authority, then you’re likely not considered registered." step-2_selected-region="Country / region of indirect tax" - step-2_indirect-tax-number="{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}}" + step-2_indirect-tax-number="{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number} other {Tax number}}" step-2_province="Province" - step-2_indirect-tax-number-error="{taxType, select, GST {GST number} HST {HST number} VAT {VAT number} CT {CT number} SST {SST number} GENERAL {Indirect tax number}} is required" + step-2_indirect-tax-number-error="{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" step-2_qst-number="QST number" step-2_is-registered-q-s-t="I am registered for QST Tax" step-2_is-registered-sub-region-income-tax="I am an individual registered for Income Tax purposes in Spain, and withholding tax will apply to any payments made to me." From a409b1a5db23212991664e110a90b9b1f9a2d292 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 20:19:36 +0000 Subject: [PATCH 61/64] publish: @saasquatch/mint-components v2.3.0-11 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index 87c448f489..001429d279 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-10", + "version": "2.3.0-11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-10", + "version": "2.3.0-11", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 8871019cbd..38f8292d66 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.3.0-10", + "version": "2.3.0-11", "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", From 7c7d4405cffac3261920ee0729d568489a6b97dd Mon Sep 17 00:00:00 2001 From: Ashley Osuna Date: Fri, 12 Jun 2026 09:53:08 -0700 Subject: [PATCH 62/64] fix: adding missing phoneExtensions & select dropdown width for user infor form --- .../src/components/tax-and-cash/phoneExtensions.ts | 12 ++++++++++++ .../sqm-user-info-form/UserInfoFormView.stories.tsx | 2 ++ .../sqm-user-info-form/sqm-user-info-form-view.tsx | 5 +---- 3 files changed, 15 insertions(+), 4 deletions(-) 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-user-info-form/UserInfoFormView.stories.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/UserInfoFormView.stories.tsx index 467e7bb825..0861eb5410 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,8 @@ 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 = [ 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 631fedbae5..e6750260ee 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 @@ -237,10 +237,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); @@ -334,6 +330,7 @@ const vanillaStyle = ` background: var(--sqm-input-background, inherit); color: var(--sqm-input-color, inherit); border:none; + min-width: max-content; } sl-menu-item::part(base) { From 9f9dcff4123b0b6bf7dc39065ee9dffe3f68630a Mon Sep 17 00:00:00 2001 From: Andy Date: Fri, 12 Jun 2026 14:24:11 -0700 Subject: [PATCH 63/64] Update phone validation to use libphonenumber-s in conjunction with Impac ts phone validation rules. Update user form spec. Only bounce to step 1 if connectionStatus !== COMPLETED --- .../useIndirectTaxForm.tsx | 2 + .../sqm-tax-and-cash/useTaxAndCash.tsx | 26 ++-- .../sqm-user-info-form-view.tsx | 7 + .../sqm-user-info-form.feature | 131 ++++++++---------- .../sqm-user-info-form/useUserInfoForm.tsx | 21 ++- .../src/components/tax-and-cash/utils.ts | 65 ++++----- 6 files changed, 135 insertions(+), 117 deletions(-) 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 92c713230a..4fc96ef0af 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 @@ -126,6 +126,8 @@ export const COMPLETE_PARTNER = gql` impactConnection { connected publisher { + phoneNumber + phoneNumberCountryCode brandedSignup requiredTaxDocumentType currentTaxDocument { 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 a895da3f90..8298966a01 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 @@ -58,17 +58,21 @@ function getCurrentStep(user: UserQuery["user"]) { phoneNumberCountryCode, } = user.impactConnection.publisher; - const hasBillingInfo = - billingAddress && - billingCity && - billingCountryCode && - billingPostalCode && - phoneNumberCountryCode && - phoneNumber && - isValidI18nPhoneNumber(phoneNumberCountryCode, phoneNumber); - - if (!hasBillingInfo) { - return "/1"; + 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 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 631fedbae5..5fc320be9e 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,4 +1,5 @@ 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"; @@ -702,6 +703,12 @@ export const UserInfoFormView = (props: UserInfoFormViewProps) => { id="phoneNumber" name="/phoneNumber" value={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; diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature index 1ce8a34434..52c6288701 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/sqm-user-info-form.feature @@ -374,8 +374,6 @@ Feature: Tax Form Step One @motivating Scenario: Phone number is validated against the selected country's rules - # Validation mirrors Impact's I18nPhoneNumberParams rules so the partner upsert - # request is not rejected by the backend after the user advances past step 1. Given the "Phone number" field is not empty And the "Extension" (phoneNumberCountryCode) field has a value selected When the user clicks "Continue" @@ -396,100 +394,92 @@ Feature: Tax Form Step One Given the "Extension" (phoneNumberCountryCode) field has value And the "Phone number" field has value When the user clicks "Continue" - Then non-digit characters in the phone number are ignored - And a single leading "1" country-code digit is ignored - And the phone number is valid only when the digit count is exactly 10 - And the phone validation result + Then the phone validation result Examples: - | countryCode | phoneNumber | isValid | - | US | 4155551234 | passes | - | US | (415) 555-1234 | passes | - | US | 415-555-1234 | passes | - | US | 14155551234 | passes | - | CA | 6045551234 | passes | - | CA | 1-604-555-1234 | passes | - | US | 415555123 | fails | - | US | 41555512345 | fails | - | US | abc | fails | - | CA | 604555123 | fails | + | countryCode | phoneNumber | isValid | + | US | 4155551234 | passes | + | US | (415) 555-1234 | passes | + | US | +1 415-555-1234 | passes | + | CA | +1 604 555 1234 | passes | + | US | 415555123 | fails | + | US | abc | fails | + | US | 0014155551234 | fails | + | US | "" | fails | @motivating Scenario Outline: Phone number validation per country (AU) Given the "Extension" (phoneNumberCountryCode) field has value "AU" And the "Phone number" field has value When the user clicks "Continue" - Then non-digit characters in the phone number are ignored - And no country-code stripping is performed - And the phone number is valid only when the digit count is exactly 9 or 10 - And the phone validation result + Then the phone validation result Examples: - | phoneNumber | isValid | - | 412345678 | passes | - | 0412345678 | passes | - | 04 1234 5678 | passes | - | 12345678 | fails | - | 04123456789 | fails | + | phoneNumber | isValid | + | 0412345678 | passes | + | 04 1234 5678 | passes | + | +61 412345678 | passes | + | +610412345678 | passes | + | 12345678 | fails | + | 04123456789 | fails | + | abcdefghi | fails | + | "" | fails | @motivating Scenario Outline: Phone number validation per country (NZ) + # Delegates to libphonenumber-js's per-country pattern check. Given the "Extension" (phoneNumberCountryCode) field has value "NZ" And the "Phone number" field has value When the user clicks "Continue" - Then non-digit characters in the phone number are ignored - And if the digit count is greater than 9 a leading "640" or a single leading "0" is stripped - And the phone number is valid only when the remaining digit count is between 8 and 10 - And the phone validation result + Then the phone validation result Examples: - | phoneNumber | isValid | - | 21234567 | passes | - | 212345678 | passes | - | 0212345678 | passes | - | 64021234567 | passes | - | 1234567 | fails | - | 02123456789 | fails | + | phoneNumber | isValid | + | 21234567 | passes | + | 0212345678 | passes | + | 02123456789 | passes | + | 021 234 567 | passes | + | +64 21 234 5678 | passes | + | +640212345678 | passes | + | 1234567 | fails | + | "" | fails | @motivating Scenario Outline: Phone number validation per country (GB) + # Delegates to libphonenumber-js's per-country pattern check. Given the "Extension" (phoneNumberCountryCode) field has value "GB" And the "Phone number" field has value When the user clicks "Continue" - Then non-digit characters in the phone number are ignored - And a single leading "0" is stripped - And the phone number is valid only when the remaining digit count is at least 6 - And no dialing-code (+44) match is enforced - And the phone validation result + Then the phone validation result Examples: - | phoneNumber | isValid | - | 2079460958 | passes | - | 02079460958 | passes | - | 020 7946 0958 | passes | - | 12345 | fails | - | 012345 | fails | + | phoneNumber | isValid | + | 2079460958 | passes | + | 02079460958 | passes | + | 020 7946 0958 | passes | + | +44 20 7946 0958 | passes | + | +99 20 7946 0958 | fails | + | 012345 | fails | + | 012 | fails | + | "" | fails | @motivating Scenario Outline: Phone number validation for all other countries Given the "Extension" (phoneNumberCountryCode) field has value And the "Phone number" field has value When the user clicks "Continue" - Then non-digit characters in the phone number are ignored - And a single leading "0" is stripped - And the phone number is valid only when the remaining digit count is at least 6 - And if the raw input begins with "+" the first whitespace-delimited token after the "+" must exactly equal the selected country's dial code - And the phone validation result + Then the phone validation result Examples: - | countryCode | phoneNumber | isValid | note | - | DE | 30123456 | passes | digits only, length >= 6 | - | DE | 030 12 34 56 | passes | leading "0" stripped | - | DE | +49 30 12 34 56 | passes | "+49" matches DE dial code | - | FR | +33 1 42 68 53 00 | passes | "+33" matches FR dial code | - | DE | +44 30 12 34 56 | fails | "+44" does not match DE dial code | - | FR | 12345 | fails | length < 6 after stripping leading "0" | - | ES | +3491 123 4567 | fails | "+3491" is not the ES dial code (must be "+34") | + | countryCode | phoneNumber | isValid | note | + | DE | +49 30 12345678 | passes | valid Berlin landline | + | FR | +33 1 42 68 53 00 | passes | valid Paris landline | + | ES | +34 911 234567 | passes | valid Madrid landline | + | DE | +44 20 7946 0958 | passes | re-resolved to GB by validator (still valid) | + | DE | +49 12345 | fails | German short code; backend would NULL it | + | DE | +49 0012345 | fails | not a valid DE pattern | + | DE | +49 30 12 | fails | too short for DE | + | DE | "" | fails | empty input | @motivating Scenario: Phone validation always runs on submit if editable @@ -518,11 +508,12 @@ Feature: Tax Form Step One And the value submitted as phoneNumberCountryCode is Examples: - | countryCode | input | submitted | - | US | +1 415 555 1234 | 415 555 1234 | - | US | 0014155551234 | 4155551234 | - | US | 4155551234 | 4155551234 | - | GB | +44 20 7946 0958 | 20 7946 0958 | - | GB | 020 7946 0958 | 020 7946 0958 | - | DE | +49 30 12 34 56 | 30 12 34 56 | - | DE | 030 12 34 56 | 030 12 34 56 | + | countryCode | input | submitted | + | US | +1 415 555 1234 | 4155551234 | + | CA | +1 604 555 1234 | 6045551234 | + | AU | 0412345678 | 412345678 | + | AU | +61 412345678 | 412345678 | + | NZ | 0212345678 | 212345678 | + | NZ | +640212345678 | 212345678 | + | GB | +44 20 7946 0958 | 2079460958 | + | DE | +49 30 12 34 56 | 30123456 | diff --git a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx index a71c1f6dc4..c56be9994a 100644 --- a/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx +++ b/packages/mint-components/src/components/tax-and-cash/sqm-user-info-form/useUserInfoForm.tsx @@ -27,7 +27,7 @@ import { UserQuery, } from "../data"; import { ADDRESS_REGIONS, AddressRegions } from "../subregions"; -import { objectIsFull, validTaxDocument } from "../utils"; +import { objectIsFull, toDomesticNumber, validTaxDocument } from "../utils"; import { TaxForm } from "./sqm-user-info-form"; import { ImpactConnection } from "../../../saasquatch"; import { TAX_FORM_UPDATED_EVENT_KEY } from "../eventKeys"; @@ -253,13 +253,16 @@ export function useUserInfoForm(props: TaxForm) { city: formData.city, state: formData.state, postalCode: formData.postalCode, - phoneNumber: formData.phoneNumber, + phoneNumber: toDomesticNumber( + formData.phoneNumberCountryCode, + formData.phoneNumber + ), phoneNumberCountryCode: formData.phoneNumberCountryCode, } as Partial; // If the partner has already been started call completeImpactPartner to // update the remaining details. Otherwise create a new connection. - const userData = data?.user; + // const userData = data?.user; let result = null; let connectionResult; result = await completeImpactPartner({ @@ -344,12 +347,17 @@ export function useUserInfoForm(props: TaxForm) { const { allowBankingCollection, ...userData } = formData; + const normalizedPhoneNumber = toDomesticNumber( + userData.phoneNumberCountryCode, + userData.phoneNumber + ); + setUserFormContext({ ...userFormContext, firstName: userData.firstName, lastName: userData.lastName, phoneNumberCountryCode: userData.phoneNumberCountryCode, - phoneNumber: userData.phoneNumber, + phoneNumber: normalizedPhoneNumber, countryCode: userData.countryCode, address: userData.address, city: userData.city, @@ -363,7 +371,10 @@ export function useUserInfoForm(props: TaxForm) { if (skipNextStep) { try { const { resultPublisher, hasValidCurrentDocument } = - await connectPartner(formData); + await connectPartner({ + ...formData, + phoneNumber: normalizedPhoneNumber, + }); if ( resultPublisher?.requiredTaxDocumentType && diff --git a/packages/mint-components/src/components/tax-and-cash/utils.ts b/packages/mint-components/src/components/tax-and-cash/utils.ts index 2e2bc3eaa8..7983bdd4e1 100644 --- a/packages/mint-components/src/components/tax-and-cash/utils.ts +++ b/packages/mint-components/src/components/tax-and-cash/utils.ts @@ -1,38 +1,54 @@ +import { CountryCode, parsePhoneNumberFromString } from "libphonenumber-js"; import { intl } from "../../global/global"; import { TaxDocumentType } from "./data"; -import { PHONE_EXTENSIONS } from "./phoneExtensions"; -function stripLeadingZero(d: string) { - return d.startsWith("0") ? d.slice(1) : d; +/** + * Normalize user input to the domestic form Impact stores: digits only, + * with the country dial code and trunk-zero prefix removed. + */ +export function toDomesticNumber( + phoneCountryCode: string | undefined, + input: string | undefined +): string { + if (!input) return ""; + const parsed = parsePhoneNumberFromString( + input, + phoneCountryCode?.toUpperCase() as CountryCode + ); + return (parsed?.nationalNumber as string) ?? input.replace(/\D/g, ""); } /** - * Mirrors Impact's I18nPhoneNumberParams validation rules per country. - * Returns true when the phone number is valid for the given the phoneNumberCountryCode country. + * Validates a phone number against both libphonenumber-js's per-country + * pattern rules AND Impact's I18nPhoneNumber length rules, applied to the + * sanitized value we'll submit. */ export function isValidI18nPhoneNumber( phoneCountryCode: string | undefined, phoneNumber: string | undefined ): boolean { - if (!phoneCountryCode || !phoneNumber?.trim()) return false; + const country = phoneCountryCode.toUpperCase() as CountryCode; + const parsed = parsePhoneNumberFromString(phoneNumber, country); + if (!parsed?.isValid()) return false; + return passesImpactBackendLengthCheck(country, toDomesticNumber(country, phoneNumber)); +} - const country = phoneCountryCode.toUpperCase(); - const digits = phoneNumber.replace(/\D/g, ""); - +/** + * Mirrors estalea.bucket.phone.I18nPhoneNumber.isValidI18nPhoneNumber() + * length rules on the already-sanitized (digits-only) submission value. + */ +function passesImpactBackendLengthCheck(country: string, digits: string): boolean { + if (!digits) return false; switch (country) { case "US": case "CA": { - // strip a single leading "1", then exactly 10 digits const n = digits.startsWith("1") ? digits.slice(1) : digits; return n.length === 10; } case "AU": - // 9 or 10 digits return digits.length === 9 || digits.length === 10; case "NZ": { - // trim "640" country prefix or leading "0" (only when >9 digits), - // then 8–10 digits let n = digits; if (n.length > 9) { if (n.startsWith("640")) n = n.slice(3); @@ -40,28 +56,15 @@ export function isValidI18nPhoneNumber( } return n.length >= 8 && n.length <= 10; } - case "GB": - // >= 6 digits after stripping a single leading "0", no dialing-code check + default: return stripLeadingZero(digits).length >= 6; - default: { - // All other countries: strip a single leading "0", then >= 6 digits - if (stripLeadingZero(digits).length < 6) return false; - // If input starts with "+", the dialing code (first whitespace-split - // token) must match the selected country exactly. Splits on first - // whitespace, so "+4420 7946 0958" supplies "4420" (not "44") and fails. - if (phoneNumber.trim().startsWith("+")) { - const supplied = phoneNumber.trim().split(/\s/)[0].slice(1); - const expected = PHONE_EXTENSIONS[country]?.dial_code?.replace( - /^\+/, - "" - ); - return expected === supplied; - } - return true; - } } } +function stripLeadingZero(digits: string): string { + return digits.startsWith("0") ? digits.slice(1) : digits; +} + export function validTaxDocument(requiredType: TaxDocumentType | undefined) { const validTypes = ["W9", "W8BENE", "W8BEN"]; if (validTypes.includes(requiredType)) return true; From 37fc7d5b7718c8560d085bda424532f1d7836821 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 21:40:16 +0000 Subject: [PATCH 64/64] publish: @saasquatch/mint-components v2.3.0-12 This commit was generated by GitHub Actions CI --- packages/mint-components/package-lock.json | 4 ++-- packages/mint-components/package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/mint-components/package-lock.json b/packages/mint-components/package-lock.json index 001429d279..4963c3c1c2 100644 --- a/packages/mint-components/package-lock.json +++ b/packages/mint-components/package-lock.json @@ -1,12 +1,12 @@ { "name": "@saasquatch/mint-components", - "version": "2.3.0-11", + "version": "2.3.0-12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@saasquatch/mint-components", - "version": "2.3.0-11", + "version": "2.3.0-12", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/packages/mint-components/package.json b/packages/mint-components/package.json index 38f8292d66..9de4a6b31a 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.3.0-11", + "version": "2.3.0-12", "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",