diff --git a/packages/mint-components/.changeset/puny-brooms-call.md b/packages/mint-components/.changeset/puny-brooms-call.md
new file mode 100644
index 000000000..663781c7d
--- /dev/null
+++ b/packages/mint-components/.changeset/puny-brooms-call.md
@@ -0,0 +1,5 @@
+---
+"@saasquatch/mint-components": minor
+---
+
+Partner info modal now shows firstName and lastName inputs when either is missing from the user
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 96c14a0c2..de13fbd03 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
@@ -27,6 +27,8 @@ const demoCurrencies = [
];
const noopCallbacks = {
+ onFirstNameChange: (e: any) => console.log("First name changed:", e),
+ onLastNameChange: (e: any) => console.log("Last name changed:", e),
onCountryChange: (e: any) => console.log("Country changed:", e),
onCurrencyChange: (e: any) => console.log("Currency changed:", e),
onCheckboxChange: (e: any) => console.log("Checkbox changed:", e),
@@ -66,6 +68,9 @@ const defaultProps: PartnerInfoModalViewProps = {
loading: false,
submitting: false,
isExistingPartner: false,
+ shouldDisplayNameFields: false,
+ firstName: "",
+ lastName: "",
countryCode: "",
currency: "",
error: "",
@@ -208,3 +213,39 @@ export const SQMComponentNewPartner = () => {
>
);
};
+
+export const PartnerHasFirstAndLastName = () => {
+ const props: PartnerInfoModalViewProps = {
+ ...defaultProps,
+ states: {
+ ...defaultProps.states,
+ firstName: "John",
+ lastName: "Doe",
+ },
+ };
+ return ;
+};
+
+export const PartnerHasFirstNameOnly = () => {
+ const props: PartnerInfoModalViewProps = {
+ ...defaultProps,
+ states: {
+ ...defaultProps.states,
+ firstName: "John",
+ lastName: "",
+ shouldDisplayNameFields: true,
+ },
+ };
+ return ;
+};
+
+export const Loading = () => {
+ const props: PartnerInfoModalViewProps = {
+ ...defaultProps,
+ states: {
+ ...defaultProps.states,
+ loading: true,
+ },
+ };
+ 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 100a57fff..1b1dc1617 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,28 @@
## Properties
-| Property | Attribute | Description | Type | Default |
-| ----------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `allowBankingCollection` | `allow-banking-collection` | Edit the property called terms and conditions text to change what's displayed for {termsAndConditionsLink}. | `string` | `"I have read the {termsAndConditionsLink} and allow impact.com to collect my tax and banking information."` |
-| `confirmButtonLabel` | `confirm-button-label` | | `string` | `"Confirm"` |
-| `countryLabel` | `country-label` | | `string` | `"Country"` |
-| `currencyLabel` | `currency-label` | | `string` | `"Currency"` |
-| `demoData` | -- | | `{ states?: { open: boolean; loading: boolean; submitting: boolean; isExistingPartner: boolean; countryCode: string; currency: string; error: string; success: boolean; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; allowBankingCollection: boolean; disabled: boolean; }; }` | `undefined` |
-| `descriptionExistingPartner` | `description-existing-partner` | Description for existing partner confirmation | `string` | `"We found an account with this email on our referral program provider, impact.com. Please confirm your country and currency now to get your future rewards faster."` |
-| `descriptionNewPartner` | `description-new-partner` | Description for new partner setup | `string` | `"Confirm your country and currency now to get your future rewards faster."` |
-| `inModal` | `in-modal` | Used to render in another modal. | `boolean` | `false` |
-| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please fill out all fields."` |
-| `modalHeader` | `modal-header` | Header text when user has no existing partner | `string` | `"Let's get you ready for rewards"` |
-| `modalHeaderExistingPartner` | `modal-header-existing-partner` | Header text when user has an existing partner | `string` | `"We found an existing account"` |
-| `networkErrorText` | `network-error-text` | | `string` | `"An error occurred. Please try again."` |
-| `searchCountryPlaceholder` | `search-country-placeholder` | | `string` | `"Search for a country"` |
-| `searchCurrencyPlaceholder` | `search-currency-placeholder` | | `string` | `"Search for a currency"` |
-| `stateController` | `state-controller` | | `string` | `'{"sqm-partner-info-modal":{"states":{"open":false}}}'` |
-| `submitButtonLabel` | `submit-button-label` | | `string` | `"Submit"` |
-| `supportDescriptionExistingPartner` | `support-description-existing-partner` | Support description for existing partner confirmation | `string` | `"If this is a mistake, please contact {supportLink} or sign up for this referral program with a different email."` |
-| `supportLink` | `support-link` | Support description for existing partner confirmation | `string` | `"Support"` |
-| `termsAndConditionsLabel` | `terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` |
-| `termsAndConditionsLink` | `terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"https://terms.advocate.impact.com/PayoutTermsAndConditions.html"` |
+| 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; shouldDisplayNameFields: boolean; firstName: string; lastName: string; countryCode: string; currency: string; error: string; success: boolean; filteredCountries: { countryCode: string; displayName: string; }[]; filteredCurrencies: { currencyCode: string; displayName: string; }[]; allowBankingCollection: boolean; disabled: boolean; }; }` | `undefined` |
+| `descriptionExistingPartner` | `description-existing-partner` | Description for existing partner confirmation | `string` | `"We found an account with this email on our referral program provider, impact.com. Please confirm your country and currency now to get your future rewards faster."` |
+| `descriptionNewPartner` | `description-new-partner` | Description for new partner setup | `string` | `"Confirm your country and currency now to get your future rewards faster."` |
+| `inModal` | `in-modal` | Used to render in another modal. | `boolean` | `false` |
+| `missingFieldsErrorText` | `missing-fields-error-text` | | `string` | `"Please fill out all fields."` |
+| `modalHeader` | `modal-header` | Header text when user has no existing partner | `string` | `"Let's get you ready for rewards"` |
+| `modalHeaderExistingPartner` | `modal-header-existing-partner` | Header text when user has an existing partner | `string` | `"We found an existing account"` |
+| `networkErrorText` | `network-error-text` | | `string` | `"An error occurred. Please try again."` |
+| `searchCountryPlaceholder` | `search-country-placeholder` | | `string` | `"Search for a country"` |
+| `searchCurrencyPlaceholder` | `search-currency-placeholder` | | `string` | `"Search for a currency"` |
+| `stateController` | `state-controller` | | `string` | `'{"sqm-partner-info-modal":{"states":{"open":false}}}'` |
+| `submitButtonLabel` | `submit-button-label` | | `string` | `"Submit"` |
+| `supportDescriptionExistingPartner` | `support-description-existing-partner` | Support description for existing partner confirmation | `string` | `"If this is a mistake, please contact {supportLink} or sign up for this referral program with a different email."` |
+| `supportLink` | `support-link` | Support description for existing partner confirmation | `string` | `"Support"` |
+| `termsAndConditionsLabel` | `terms-and-conditions-label` | The link text that appears in the terms and conditions checkbox | `string` | `"terms and conditions"` |
+| `termsAndConditionsLink` | `terms-and-conditions-link` | The link that appears in the terms and conditions checkbox | `string` | `"https://terms.advocate.impact.com/PayoutTermsAndConditions.html"` |
## Dependencies
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 ca6046dc9..c3a883a26 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
@@ -1,4 +1,4 @@
-import { h } from "@stencil/core";
+import { Fragment, h } from "@stencil/core";
import { createStyleSheet } from "../../styling/JSS";
import { intl } from "../../global/global";
@@ -8,6 +8,9 @@ export interface PartnerInfoModalViewProps {
loading: boolean;
submitting: boolean;
isExistingPartner: boolean;
+ shouldDisplayNameFields: boolean;
+ firstName: string;
+ lastName: string;
countryCode: string;
currency: string;
error: string;
@@ -18,6 +21,8 @@ export interface PartnerInfoModalViewProps {
disabled: boolean;
};
callbacks: {
+ onFirstNameChange: (e: any) => void;
+ onLastNameChange: (e: any) => void;
onCountryChange: (e: any) => void;
onCurrencyChange: (e: any) => void;
onCheckboxChange: (e: any) => void;
@@ -72,6 +77,12 @@ const style = {
gap: "var(--sl-spacing-medium)",
marginTop: "var(--sl-spacing-large)",
},
+ NameInput: {
+ "&::part(label)": {
+ fontWeight: "var(--sl-font-weight-normal)",
+ fontSize: "var(--sl-input-label-font-size-medium)",
+ },
+ },
ErrorMessage: {
marginTop: "var(--sl-spacing-x-small)",
},
@@ -132,7 +143,7 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) {
{text.supportLink}
),
- },
+ }
);
const description = states.isExistingPartner ? (
@@ -161,14 +172,36 @@ export function PartnerInfoModalContentView(props: PartnerInfoModalViewProps) {
{text.termsAndConditionsLabel}
),
- },
+ }
);
return (
-
+
{description}
+ {states.shouldDisplayNameFields && (
+
+
+
+
+ )}
and lastName=
+ When the partner-info modal opens
+ Then the "First name" input is rendered and enabled
+ And the "Last name" input is rendered and enabled
+ And the "First name" input is prefilled with
+ And the "Last name" input is prefilled with
+
+ Examples:
+ | firstName | lastName |
+ | | Doe |
+ | Jane | |
+ | | |
+
+ @motivating
+ Scenario: Name inputs are required to submit when they are shown
+ Given the partner-info modal is open with the "First name" and "Last name" inputs rendered
+ And the "Country", "Currency", and "Tax and banking" fields are all valid
+ When either the "First name" or "Last name" input is empty
+ Then the primary button is disabled
+ When both inputs have a non-empty value
+ Then the primary button becomes enabled
+
@minutia
Scenario: Submit button is disabled
Given the partner-info modal is open
- When any one of (country, currency, allowBankingCollection) is missing
+ When any one of (country, currency, allowBankingCollection, firstName, lastName) is missing
Then the submit button is disabled
@motivating
Scenario: Submitting the modal starts the Impact connection and closes the modal
- Given the user has selected country "US", currency "USD", and checked the T&C checkbox
+ Given the participant's user record has a non-empty `firstName` and `lastName`
+ And the user has selected country "US", currency "USD", and checked the T&C checkbox
When they click the primary button
Then the modal calls the `startImpactConnection` mutation with
| user.id | the participant id |
@@ -103,6 +135,23 @@ Feature: Partner Info Modal — country, currency, and T&C collection
And on success `impactConnection.connectionStatus` changes from "NOT_STARTED" to "STARTED"
And the modal closes
+ @motivating
+ Scenario: Submitting the modal sends the entered firstName and lastName when the name inputs are shown
+ Given the participant's user record is missing `firstName` and/or `lastName`
+ And the "First name" input value is "Jane"
+ And the "Last name" input value is "Doe"
+ And the user has selected country "US", currency "USD", and checked the T&C checkbox
+ When they click the primary button
+ Then the modal calls the `startImpactConnection` mutation with
+ | user.id | the participant id |
+ | user.accountId | the participant accountId |
+ | firstName | "Jane" |
+ | lastName | "Doe" |
+ | countryCode | "US" |
+ | currency | "USD" |
+ And on success `impactConnection.connectionStatus` changes from "NOT_STARTED" to "STARTED"
+ And the modal closes
+
@motivating
Scenario: Existing-partner submission attaches the participant to the linked publisher
Given the participant is linked to an existing publisher with country "CA" and currency "CAD"
@@ -130,6 +179,7 @@ Feature: Partner Info Modal — country, currency, and T&C collection
Given the partner-info modal is open
When the user clicks the primary button and the mutation is in flight
Then the "Country", "Currency", and "Tax and banking" inputs are all disabled
+ And the "First name" and "Last name" inputs are disabled when they are rendered
And the primary button shows a loading spinner
@minutia
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 f6ba8572e..7132d4f6a 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
@@ -102,7 +102,7 @@ export type CountriesQuery = {
};
export function usePartnerInfoModal(
- props: PartnerInfoModal,
+ props: PartnerInfoModal
): PartnerInfoModalViewProps {
const locale = useLocale();
@@ -118,29 +118,32 @@ export function usePartnerInfoModal(
const { data: currenciesData, loading: currenciesLoading } = useQuery(
GET_CURRENCIES,
- { variables: { locale } },
+ { variables: { locale } }
);
const { data: countriesData, loading: countriesLoading } = useQuery(
GET_COUNTRIES,
- {},
+ {}
);
+ const [shouldDisplayNameFields, setShouldDisplayNameFields] = useState(false);
+ const [firstName, setFirstName] = useState(user?.firstName ?? "");
+ const [lastName, setLastName] = useState(user?.lastName ?? "");
+
// No pre-filled country, use locale to determine countryCode instead
const [countryCode, setCountryCode] = useState(
- user?.impactConnection?.publisher?.countryCode ||
- locale.replace(/^.*_/, ""),
+ user?.impactConnection?.publisher?.countryCode || locale.replace(/^.*_/, "")
);
const [currency, setCurrency] = useState(
- user?.impactConnection?.publisher?.currency || "",
+ user?.impactConnection?.publisher?.currency || ""
);
const { data: financeNetworkData } = useQuery(
GET_FINANCE_NETWORK_SETTINGS,
{
variables: { filter: countryCode ? { countryCode_eq: countryCode } : {} },
- },
+ }
);
const [startImpactConnection, { loading: connectLoading }] =
@@ -171,14 +174,14 @@ export function usePartnerInfoModal(
(agg, settings) => {
if (countryCode && settings.countryCode !== countryCode) return agg;
const c = currenciesData?.currencies?.data?.find(
- (cur) => cur.currencyCode === settings.currency,
+ (cur) => cur.currencyCode === settings.currency
);
if (!c) return agg;
if (agg.find((cur) => cur.currencyCode === settings.currency))
return agg;
return [...agg, c];
},
- [],
+ []
);
return allValidCurrencies || [];
}, [financeNetworkData, currenciesData, countryCode, isExistingPartner]);
@@ -186,27 +189,34 @@ export function usePartnerInfoModal(
const currencies = useMemo(
() =>
[..._currencies].sort((a, b) =>
- a.displayName.localeCompare(b.displayName),
+ a.displayName.localeCompare(b.displayName)
),
- [_currencies],
+ [_currencies]
);
const [countrySearch, setCountrySearch] = useState("");
const [currencySearch, setCurrencySearch] = useState("");
const [filteredCountries, setFilteredCountries] = useState(countries || []);
const [filteredCurrencies, setFilteredCurrencies] = useState(
- currencies || [],
+ currencies || []
);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
-
+ const [initialized, setInitialized] = useState(false);
+
useEffect(() => {
- const publisher = user?.impactConnection?.publisher;
- if (!userData || !publisher) return;
- setCountryCode(publisher.countryCode);
- setCurrency(publisher.currency);
- }, [userData, user]);
+ if (initialized || !user) return;
+ setShouldDisplayNameFields(!user.firstName || !user.lastName);
+ setFirstName(user.firstName || "");
+ setLastName(user.lastName || "");
+ const publisher = user.impactConnection?.publisher;
+ if (publisher) {
+ setCountryCode(publisher.countryCode);
+ setCurrency(publisher.currency);
+ }
+ setInitialized(true);
+ }, [user, initialized]);
useEffect(() => {
if (!countries?.length) return;
@@ -215,8 +225,8 @@ export function usePartnerInfoModal(
} else {
setFilteredCountries(
countries.filter((c) =>
- c.displayName.toLowerCase().includes(countrySearch.toLowerCase()),
- ) || [],
+ c.displayName.toLowerCase().includes(countrySearch.toLowerCase())
+ ) || []
);
}
}, [countrySearch, countries]);
@@ -228,14 +238,24 @@ export function usePartnerInfoModal(
} else {
setFilteredCurrencies(
currencies.filter((c) =>
- c.currencyCode.toLowerCase().includes(currencySearch.toLowerCase()),
- ) || [],
+ c.currencyCode.toLowerCase().includes(currencySearch.toLowerCase())
+ ) || []
);
}
}, [currencySearch, currencies]);
const impactConnection = user?.impactConnection;
+ function onFirstNameChange(e: any) {
+ const value = e.target.value;
+ setFirstName(value);
+ }
+
+ function onLastNameChange(e: any) {
+ const value = e.target.value;
+ setLastName(value);
+ }
+
function onCountryChange(e: any) {
const value = e.detail?.item?.__value;
if (!value) return;
@@ -257,7 +277,12 @@ export function usePartnerInfoModal(
}
async function onSubmit() {
- if (!allowBankingCollection || !countryCode || !currency) {
+ if (
+ !allowBankingCollection ||
+ !countryCode ||
+ !currency ||
+ (shouldDisplayNameFields && (!firstName || !lastName))
+ ) {
setError(props.missingFieldsErrorText);
return;
}
@@ -274,8 +299,8 @@ export function usePartnerInfoModal(
id: user.id,
accountId: user.accountId,
},
- firstName: user.firstName,
- lastName: user.lastName,
+ firstName,
+ lastName,
countryCode,
currency,
};
@@ -297,7 +322,7 @@ export function usePartnerInfoModal(
setError(validationMsg || props.networkErrorText);
console.error(
"Failed to create Impact connection:",
- connectionResult?.validationErrors,
+ connectionResult?.validationErrors
);
return;
}
@@ -323,7 +348,10 @@ export function usePartnerInfoModal(
open: showModal,
loading: userLoading || countriesLoading || currenciesLoading,
submitting: connectLoading,
+ shouldDisplayNameFields,
isExistingPartner,
+ firstName,
+ lastName,
countryCode,
currency,
error,
@@ -334,6 +362,8 @@ export function usePartnerInfoModal(
disabled: userLoading || connectLoading,
},
callbacks: {
+ onFirstNameChange,
+ onLastNameChange,
onCountryChange,
onCurrencyChange,
onCheckboxChange,