@thecb/components 2.2.1 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/workflows/bump-version.yml +30 -0
  2. package/.github/workflows/create-release/build-body.sh +35 -0
  3. package/.github/workflows/create-release.yml +52 -0
  4. package/.github/workflows/disabled-workflows/publish-update.yml +73 -0
  5. package/README.md +68 -90
  6. package/dist/index.cjs.js +31180 -3325
  7. package/package.json +15 -35
  8. package/rollup.config.js +3 -1
  9. package/src/components/atoms/breadcrumb/Breadcrumb.js +0 -2
  10. package/src/components/atoms/button-with-action/ButtonWithAction.js +30 -4
  11. package/src/components/atoms/button-with-action/ButtonWithAction.theme.js +64 -234
  12. package/src/components/atoms/formatted-credit-card/FormattedCreditCard.js +53 -0
  13. package/src/components/atoms/formatted-credit-card/FormattedCreditCard.theme.js +9 -0
  14. package/src/components/atoms/formatted-credit-card/index.js +3 -0
  15. package/src/components/atoms/icons/AccountNumberImage.js +95 -0
  16. package/src/components/atoms/icons/BankIcon.js +82 -0
  17. package/src/components/atoms/icons/CheckmarkIcon.js +55 -0
  18. package/src/components/atoms/icons/GenericCard.js +39 -0
  19. package/src/components/atoms/icons/PaymentIcon.js +50 -0
  20. package/src/components/atoms/icons/RoutingNumberImage.js +95 -0
  21. package/src/components/atoms/icons/index.js +14 -1
  22. package/src/components/atoms/index.js +3 -0
  23. package/src/components/atoms/jumbo/Jumbo.js +76 -0
  24. package/src/components/atoms/jumbo/index.js +3 -0
  25. package/src/components/atoms/layouts/Box.js +0 -2
  26. package/src/components/atoms/layouts/Box.styled.js +1 -17
  27. package/src/components/atoms/layouts/Motion.styled.js +2 -5
  28. package/src/components/atoms/link/ExternalLink.js +3 -3
  29. package/src/components/atoms/link/ExternalLink.styled.js +9 -2
  30. package/src/components/atoms/link/InternalLink.js +2 -4
  31. package/src/components/atoms/link/InternalLink.styled.js +13 -15
  32. package/src/components/atoms/link/Link.theme.js +7 -1
  33. package/src/components/atoms/loading/Loading.js +17 -0
  34. package/src/components/atoms/loading/index.js +3 -0
  35. package/src/components/atoms/nav-header/NavHeader.js +1 -1
  36. package/src/components/atoms/placeholder/Placeholder.js +2 -1
  37. package/src/components/atoms/text/Text.js +0 -2
  38. package/src/components/atoms/text/Text.styled.js +2 -8
  39. package/src/components/atoms/toggle-switch/ToggleSwitch.js +1 -1
  40. package/src/components/index.js +1 -0
  41. package/src/components/molecules/account-and-routing-modal/AccountAndRoutingModal.js +74 -0
  42. package/src/components/molecules/account-and-routing-modal/AccountAndRoutingModal.theme.js +24 -0
  43. package/src/components/molecules/account-and-routing-modal/index.js +3 -0
  44. package/src/components/molecules/address-form/AddressForm.js +2 -1
  45. package/src/components/molecules/address-form/index.js +6 -6
  46. package/src/components/molecules/change-password-form/ChangePasswordForm.js +2 -1
  47. package/src/components/molecules/change-password-form/index.js +1 -1
  48. package/src/components/molecules/collapsible-section/CollapsibleSection.js +1 -1
  49. package/src/components/molecules/edit-name-form/EditNameForm.js +2 -1
  50. package/src/components/molecules/edit-name-form/index.js +1 -1
  51. package/src/components/molecules/editable-list/EditableList.js +139 -0
  52. package/src/components/molecules/editable-list/EditableList.styled.js +31 -0
  53. package/src/components/molecules/editable-list/index.js +3 -0
  54. package/src/components/molecules/editable-table/EditableTable.js +30 -0
  55. package/src/components/molecules/editable-table/EditableTable.styled.js +80 -0
  56. package/src/components/molecules/editable-table/TableListItem.js +64 -0
  57. package/src/components/molecules/editable-table/index.js +4 -0
  58. package/src/components/molecules/email-form/EmailForm.js +2 -1
  59. package/src/components/molecules/email-form/index.js +1 -1
  60. package/src/components/molecules/forgot-password-form/ForgotPasswordForm.js +2 -1
  61. package/src/components/molecules/forgot-password-form/index.js +1 -1
  62. package/src/components/molecules/index.js +5 -0
  63. package/src/components/molecules/login-form/LoginForm.js +2 -1
  64. package/src/components/molecules/login-form/index.js +1 -1
  65. package/src/components/molecules/module/Module.js +1 -3
  66. package/src/components/molecules/partial-amount-form/PartialAmountForm.js +73 -0
  67. package/src/components/molecules/partial-amount-form/PartialAmountForm.state.js +51 -0
  68. package/src/components/molecules/partial-amount-form/index.js +4 -0
  69. package/src/components/molecules/payment-form-ach/PaymentFormACH.js +189 -0
  70. package/src/components/molecules/payment-form-ach/PaymentFormACH.state.js +38 -0
  71. package/src/components/molecules/payment-form-ach/index.js +11 -0
  72. package/src/components/molecules/payment-form-card/PaymentFormCard.js +132 -0
  73. package/src/components/molecules/payment-form-card/PaymentFormCard.state.js +39 -0
  74. package/src/components/molecules/payment-form-card/index.js +11 -0
  75. package/src/components/molecules/phone-form/PhoneForm.js +2 -1
  76. package/src/components/molecules/phone-form/index.js +1 -1
  77. package/src/components/molecules/radio-section/RadioSection.js +1 -1
  78. package/src/components/molecules/registration-form/RegistrationForm.js +2 -1
  79. package/src/components/molecules/registration-form/index.js +1 -1
  80. package/src/components/molecules/reset-password-form/ResetPasswordForm.js +3 -1
  81. package/src/components/molecules/reset-password-form/index.js +1 -1
  82. package/src/components/molecules/tab-sidebar/TabSidebar.js +10 -5
  83. package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.js +0 -1
  84. package/src/constants/index.js +4 -0
  85. package/src/index.js +3 -1
  86. package/src/util/formats.js +54 -2
  87. package/src/util/general.js +27 -4
  88. package/src/util/index.js +4 -0
  89. package/src/util/inputValidationUtils.js +0 -167
  90. package/src/util/router-utils.js +0 -23
@@ -0,0 +1,31 @@
1
+ import styled from "styled-components";
2
+ import { WHITE } from "../../../constants/colors";
3
+
4
+ export const EditableListItem = styled.div`
5
+ box-sizing: border-box;
6
+ background: ${WHITE};
7
+ height: ${({ listItemSize }) => (listItemSize === "big" ? "120px" : "72px")};
8
+ display: flex;
9
+ justify-content: space-between;
10
+ align-items: center;
11
+ padding: 1.5rem;
12
+ :not(:last-child),
13
+ :not(:first-child) {
14
+ box-shadow: inset 0px -1px 0px 0px rgb(202, 206, 216);
15
+ }
16
+ :first-child {
17
+ border-top-left-radius: 3px;
18
+ border-top-right-radius: 3px;
19
+ }
20
+ :last-child {
21
+ border-bottom-left-radius: 3px;
22
+ border-bottom-right-radius: 3px;
23
+ box-shadow: none;
24
+ }
25
+ `;
26
+
27
+ export const EditableListItemControls = styled.div`
28
+ display: flex;
29
+ justify-content: space-evenly;
30
+ align-items: center;
31
+ `;
@@ -0,0 +1,3 @@
1
+ import EditableList from "./EditableList";
2
+
3
+ export default EditableList;
@@ -0,0 +1,30 @@
1
+ import React, { Fragment } from "react";
2
+ import { EditableTableContainer, TableWrapper } from "./EditableTable.styled";
3
+ import { Box } from "../../atoms/layouts";
4
+ import Paragraph from "../../atoms/paragraph";
5
+ import { GHOST_GREY } from "../../../constants/colors";
6
+ import { FONT_WEIGHT_SEMIBOLD } from "../../../constants/style_constants";
7
+ import { safeChildren } from "../../../util/general";
8
+
9
+ const EditableTable = ({ title, renderItem, items, isMobile }) => {
10
+ const titleChild = title && (
11
+ <Box
12
+ padding={"0 0 0.5rem 0.5rem"}
13
+ borderSize="1px"
14
+ borderColor={GHOST_GREY}
15
+ borderWidthOverride="0 0 1px 0"
16
+ >
17
+ <Paragraph variant="pL" weight={FONT_WEIGHT_SEMIBOLD}>
18
+ {title}
19
+ </Paragraph>
20
+ </Box>
21
+ );
22
+ return (
23
+ <EditableTableContainer isMobile={isMobile}>
24
+ {safeChildren(titleChild, <Fragment />)}
25
+ <TableWrapper>{renderItem(items)}</TableWrapper>
26
+ </EditableTableContainer>
27
+ );
28
+ };
29
+
30
+ export default EditableTable;
@@ -0,0 +1,80 @@
1
+ import styled from "styled-components";
2
+ import {
3
+ BRIGHT_GREY,
4
+ STORM_GREY,
5
+ GHOST_GREY,
6
+ MATISSE_BLUE
7
+ } from "../../../constants/colors";
8
+
9
+ export const EditableTableContainer = styled.div`
10
+ display: ${({ hide }) => (hide ? "none" : "flex")};
11
+ flex-direction: column;
12
+ flex: 1;
13
+ `;
14
+
15
+ export const EditableTableListItem = styled.div`
16
+ width: 100%;
17
+ display: flex;
18
+ ${({ isMobile }) => isMobile && "justify-content: center"};
19
+ align-items: ${({ isMobile }) => (isMobile ? "flex-start" : "center")};
20
+ flex-direction: ${({ isMobile }) => (isMobile ? "column" : "row")};
21
+ flex: 1;
22
+ ${({ isMobile }) =>
23
+ isMobile ? "padding: 1rem 0.5rem" : "padding: 0 0.5rem"};
24
+ `;
25
+
26
+ export const EditableListItemControls = styled.div`
27
+ display: flex;
28
+ justify-content: space-evenly;
29
+ align-items: center;
30
+ `;
31
+
32
+ export const EditableListAction = styled.div`
33
+ color: ${MATISSE_BLUE};
34
+ align-items: center;
35
+ font-size: 1rem;
36
+ padding-right: 1rem;
37
+ cursor: pointer;
38
+ display: ${({ hide }) => (hide ? "none" : "flex")};
39
+ `;
40
+
41
+ export const ItemWrapper = styled.div`
42
+ display: flex;
43
+ flex-direction: row;
44
+ flex: 1;
45
+ width: 100%;
46
+ border-bottom: 1px solid ${GHOST_GREY};
47
+ `;
48
+
49
+ export const ActionWrapper = styled.div`
50
+ display: flex;
51
+ align-self: center;
52
+ justify-content: flex-end;
53
+ ${({ isMobile }) => isMobile && `display: none`};
54
+ flex: 1;
55
+ `;
56
+
57
+ export const TableItemKey = styled.div`
58
+ display: flex;
59
+ ${({ isMobile }) => !isMobile && "flex: 1"};
60
+ ${({ isMobile }) => isMobile && "align-items: center"};
61
+ ${({ isMobile }) => !isMobile && "padding: 1.25rem 0"};
62
+ font-size: ${({ isMobile }) => (isMobile ? "1rem" : "1.125rem")};
63
+ color: ${STORM_GREY};
64
+ `;
65
+
66
+ export const TableItemValue = styled.div`
67
+ display: flex;
68
+ ${({ isMobile }) => !isMobile && "flex: 1"};
69
+ ${({ isMobile }) => !isMobile && "padding: 1.25rem 0"};
70
+ ${({ isMobile }) => isMobile && "align-items: center"};
71
+ font-size: ${({ isMobile }) => (isMobile ? "1.125rem" : "1.0625rem")};
72
+ color: ${BRIGHT_GREY};
73
+ `;
74
+
75
+ export const TableWrapper = styled.div`
76
+ display: flex;
77
+ flex-direction: row;
78
+ flex: 1;
79
+ width: 100%;
80
+ `;
@@ -0,0 +1,64 @@
1
+ import React, { Fragment } from "react";
2
+ import {
3
+ EditableTableListItem,
4
+ EditableListAction,
5
+ ItemWrapper,
6
+ ActionWrapper,
7
+ TableItemKey,
8
+ TableItemValue
9
+ } from "./EditableTable.styled";
10
+ import { Box } from "../../atoms/layouts";
11
+ import Text from "../../atoms/text";
12
+ import { CHARADE_GREY } from "../../../constants/colors";
13
+
14
+ const TableListItem = ({
15
+ title,
16
+ value,
17
+ canEdit = false,
18
+ canRemove = false,
19
+ isMobile
20
+ }) => (
21
+ <Box
22
+ padding="0px"
23
+ extraStyles={`&:last-child {
24
+ > * {
25
+ border-bottom: none;
26
+ }
27
+ }`}
28
+ >
29
+ <ItemWrapper>
30
+ <EditableTableListItem isMobile={isMobile}>
31
+ <TableItemKey isMobile={isMobile}>
32
+ <Text variant="pS" color={CHARADE_GREY} aria-level="3">
33
+ {title}
34
+ </Text>
35
+ </TableItemKey>
36
+ <TableItemValue isMobile={isMobile}>
37
+ <Text variant="p" color={CHARADE_GREY}>
38
+ {value}
39
+ </Text>
40
+ </TableItemValue>
41
+ {canRemove || canEdit ? (
42
+ <ActionWrapper isMobile={isMobile}>
43
+ <EditableListAction
44
+ hide={!canRemove}
45
+ onClick={() => console.log("Remove Item Coming Soon...")}
46
+ >
47
+ Remove
48
+ </EditableListAction>
49
+ <EditableListAction
50
+ hide={!canEdit}
51
+ onClick={() => console.log("Edit Item Coming Soon...")}
52
+ >
53
+ Edit
54
+ </EditableListAction>
55
+ </ActionWrapper>
56
+ ) : (
57
+ <Fragment />
58
+ )}
59
+ </EditableTableListItem>
60
+ </ItemWrapper>
61
+ </Box>
62
+ );
63
+
64
+ export default TableListItem;
@@ -0,0 +1,4 @@
1
+ import EditableTable from "./EditableTable";
2
+ import TableListItem from "./TableListItem";
3
+
4
+ export { EditableTable, TableListItem };
@@ -5,6 +5,7 @@ import {
5
5
  FormContainer,
6
6
  FormInputColumn
7
7
  } from "../../atoms/form-layouts";
8
+ import { noop } from "../../../util/general";
8
9
 
9
10
  const EmailForm = ({
10
11
  variant = "default",
@@ -12,7 +13,7 @@ const EmailForm = ({
12
13
  fields,
13
14
  actions,
14
15
  showErrors,
15
- handleSubmit
16
+ handleSubmit = noop
16
17
  }) => {
17
18
  if (clearOnDismount) {
18
19
  useEffect(() => () => actions.form.clear(), []);
@@ -2,7 +2,7 @@ import EmailForm from "./EmailForm";
2
2
  import {
3
3
  reducer,
4
4
  mapStateToProps,
5
- mapDispatchToProps,
5
+ mapDispatchToProps
6
6
  } from "./EmailForm.state";
7
7
 
8
8
  EmailForm.reducer = reducer;
@@ -1,13 +1,14 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { required, isProbablyEmail } from "redux-freeform";
3
3
  import { FormInput } from "../../atoms/form-layouts";
4
+ import { noop } from "../../../util/general";
4
5
 
5
6
  const ForgotPasswordForm = ({
6
7
  fields,
7
8
  actions,
8
9
  clearOnDismount,
9
10
  showErrors,
10
- handleSubmit
11
+ handleSubmit = noop
11
12
  }) => {
12
13
  if (clearOnDismount) {
13
14
  useEffect(() => () => actions.form.clear(), []);
@@ -2,7 +2,7 @@ import ForgotPasswordForm from "./ForgotPasswordForm";
2
2
  import {
3
3
  reducer,
4
4
  mapStateToProps,
5
- mapDispatchToProps,
5
+ mapDispatchToProps
6
6
  } from "./ForgotPasswordForm.state";
7
7
 
8
8
  ForgotPasswordForm.reducer = reducer;
@@ -2,6 +2,8 @@ export { default as AddressForm } from "./address-form";
2
2
  export { default as ChangePasswordForm } from "./change-password-form";
3
3
  export { default as CollapsibleSection } from "./collapsible-section";
4
4
  export { default as EditNameForm } from "./edit-name-form";
5
+ export { default as EditableList } from "./editable-list";
6
+ export * from "./editable-table";
5
7
  export { default as EmailForm } from "./email-form";
6
8
  export { default as ForgotPasswordForm } from "./forgot-password-form";
7
9
  export { default as HighlightTabRow } from "./highlight-tab-row";
@@ -10,8 +12,11 @@ export { default as Modal } from "./modal";
10
12
  export { default as Module } from "./module";
11
13
  export * from "./nav-menu";
12
14
  export { default as Obligation } from "./obligation";
15
+ export * from "./partial-amount-form";
13
16
  export { default as PaymentButtonBar } from "./payment-button-bar";
14
17
  export { default as PaymentDetails } from "./payment-details";
18
+ export { default as PaymentFormACH } from "./payment-form-ach";
19
+ export { default as PaymentFormCard } from "./payment-form-card";
15
20
  export { default as PhoneForm } from "./phone-form";
16
21
  export { default as RadioSection } from "./radio-section";
17
22
  export { default as RegistrationForm } from "./registration-form";
@@ -1,13 +1,14 @@
1
1
  import React, { useEffect } from "react";
2
2
  import { required, isProbablyEmail } from "redux-freeform";
3
3
  import { FormInput, FormInputColumn } from "../../atoms/form-layouts";
4
+ import { noop } from "../../../util/general";
4
5
 
5
6
  const LoginForm = ({
6
7
  clearOnDismount,
7
8
  fields,
8
9
  actions,
9
10
  showErrors,
10
- handleSubmit
11
+ handleSubmit = noop
11
12
  }) => {
12
13
  if (clearOnDismount) {
13
14
  useEffect(() => () => actions.form.clear(), []);
@@ -2,7 +2,7 @@ import LoginForm from "./LoginForm";
2
2
  import {
3
3
  reducer,
4
4
  mapStateToProps,
5
- mapDispatchToProps,
5
+ mapDispatchToProps
6
6
  } from "./LoginForm.state";
7
7
 
8
8
  LoginForm.reducer = reducer;
@@ -1,8 +1,6 @@
1
- import React from "react";
2
-
1
+ import React, { Fragment } from "react";
3
2
  import Heading from "../../atoms/heading";
4
3
  import { Box } from "../../atoms/layouts";
5
- import { Fragment } from "react";
6
4
 
7
5
  const Module = ({
8
6
  heading,
@@ -0,0 +1,73 @@
1
+ import React from "react";
2
+ import { required, numberGreaterThan, numberLessThan } from "redux-freeform";
3
+ import { displayCurrency } from "../../../util/general";
4
+ import Text from "../../atoms/text";
5
+ import {
6
+ FormInput,
7
+ FormInputColumn,
8
+ FormContainer
9
+ } from "../../atoms/form-layouts";
10
+ import { moneyFormat } from "../../../util/formats";
11
+
12
+ const PartialAmountForm = ({
13
+ variant = "default",
14
+ lineItems,
15
+ maximum,
16
+ minimum = 1,
17
+ clearOnDismount,
18
+ fields,
19
+ actions,
20
+ showErrors = false
21
+ }) => {
22
+ if (clearOnDismount) {
23
+ useEffect(() => () => actions.form.clear(), []);
24
+ }
25
+
26
+ const amountErrors = {
27
+ [required.error]: "Amount is required",
28
+ [numberGreaterThan.error]: `Your total payment must be greater than ${displayCurrency(
29
+ minimum
30
+ )}`,
31
+ [numberLessThan.error]: `Your total payment must be less than ${displayCurrency(
32
+ maximum
33
+ )}`
34
+ };
35
+
36
+ const lineItemsNew = Array.isArray(lineItems) ? lineItems : [];
37
+ return (
38
+ <FormContainer variant={variant} role="form" aria-label="Partial amount">
39
+ <Text variant="p">
40
+ Pay a portion of your current bill. The remaining balance will still be
41
+ due on the same due date.
42
+ </Text>
43
+ <div style={{ height: "16px" }}></div>
44
+ <FormInputColumn>
45
+ {lineItemsNew.map(li => (
46
+ <FormInput
47
+ labelTextWhenNoError={li.description}
48
+ key={li.id}
49
+ field={fields[li.id]}
50
+ fieldActions={actions.fields[li.id]}
51
+ showErrors={showErrors}
52
+ errorMessages={amountErrors}
53
+ style={{ textAlign: "right" }}
54
+ placeholder="$0.00"
55
+ formatter={moneyFormat}
56
+ decorator={
57
+ <Text variant="p">
58
+ Amount owed:{" "}
59
+ {
60
+ <Text variant="p" weight="600">
61
+ {displayCurrency(li.amount)}
62
+ </Text>
63
+ }
64
+ </Text>
65
+ }
66
+ />
67
+ ))}
68
+ </FormInputColumn>
69
+ </FormContainer>
70
+ );
71
+ };
72
+
73
+ export default PartialAmountForm;
@@ -0,0 +1,51 @@
1
+ import {
2
+ createFormState,
3
+ required,
4
+ onlyNaturals,
5
+ validateSum,
6
+ numberGreaterThan,
7
+ numberLessThan
8
+ } from "redux-freeform";
9
+
10
+ export const createPartialAmountFormState = (
11
+ lineItems,
12
+ maximum,
13
+ minimum = 1
14
+ ) => {
15
+ const formConfig = lineItems.reduce((acc, item) => {
16
+ const validators = [
17
+ required(),
18
+ validateSum(
19
+ numberGreaterThan(minimum),
20
+ lineItems
21
+ .filter(lineItem => lineItem != item)
22
+ .reduce((acc, curr) => [...acc, curr.id], [])
23
+ )
24
+ ];
25
+ if (!!maximum) {
26
+ validators.push(
27
+ validateSum(
28
+ numberLessThan(maximum),
29
+ lineItems
30
+ .filter(lineItem => lineItem != item)
31
+ .reduce((acc, curr) => [...acc, curr.id], [])
32
+ )
33
+ );
34
+ }
35
+ return {
36
+ ...acc,
37
+ [item.id]: {
38
+ validators: validators,
39
+ constraints: [onlyNaturals()]
40
+ }
41
+ };
42
+ }, {});
43
+ const { reducer, mapStateToProps, mapDispatchToProps } = createFormState(
44
+ formConfig
45
+ );
46
+ return {
47
+ partialAmountFormReducer: reducer,
48
+ partialAmountFormMapStateToProps: mapStateToProps,
49
+ partialAmountFormMapDispatchToProps: mapDispatchToProps
50
+ };
51
+ };
@@ -0,0 +1,4 @@
1
+ import PartialAmountForm from "./PartialAmountForm";
2
+ import { createPartialAmountFormState } from "./PartialAmountForm.state";
3
+
4
+ export { PartialAmountForm, createPartialAmountFormState };
@@ -0,0 +1,189 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import styled from "styled-components";
3
+ import { ifElse, isNil, always } from "ramda";
4
+ import Checkbox from "../../atoms/checkbox";
5
+ import {
6
+ required,
7
+ matchesField,
8
+ hasLength,
9
+ isRoutingNumber
10
+ } from "redux-freeform";
11
+ import FormSelect from "../../atoms/form-select";
12
+ import {
13
+ FormInput,
14
+ FormInputColumn,
15
+ FormContainer
16
+ } from "../../atoms/form-layouts";
17
+ import Alert from "../../atoms/alert";
18
+ import AccountAndRoutingModal from "../account-and-routing-modal";
19
+ import { displayCurrency, noop } from "../../../util/general";
20
+
21
+ const ModalWrapper = styled.div``;
22
+
23
+ const PaymentFormACH = ({
24
+ variant = "default",
25
+ defaultMethod,
26
+ toggleCheckbox,
27
+ hideDefaultPayment = true,
28
+ allowBankAccountType,
29
+ clearOnDismount,
30
+ fields,
31
+ actions,
32
+ showErrors,
33
+ fees,
34
+ handleSubmit = noop
35
+ }) => {
36
+ if (clearOnDismount) {
37
+ useEffect(() => () => actions.form.clear(), []);
38
+ }
39
+ const [showRouting, toggleShowRouting] = useState(false);
40
+ const [showAccount, toggleShowAccount] = useState(false);
41
+
42
+ const nameErrors = {
43
+ [required.error]: "Name is required"
44
+ };
45
+ const routingNumberErrors = {
46
+ [required.error]: "Routing number is required",
47
+ [hasLength.error]: "Routing number must be 9 digits",
48
+ [isRoutingNumber.error]: "Invalid routing number"
49
+ };
50
+ const confirmRoutingNumberErrors = {
51
+ [matchesField.error]:
52
+ "Confirm routing number field must match routing number"
53
+ };
54
+ const accountNumberErrors = {
55
+ [required.error]: "Account number is required",
56
+ [hasLength.error]: "Account number must be between 6 and 17 digits"
57
+ };
58
+ const confirmAccountNumberErrors = {
59
+ [matchesField.error]:
60
+ "Confirm account number field must match account number"
61
+ };
62
+ const accountTypeErrors = {
63
+ [required.error]: "Account type is required"
64
+ };
65
+
66
+ return (
67
+ <FormContainer variant={variant} role="form" aria-label="ACH Payment">
68
+ <FormInputColumn>
69
+ <FormInput
70
+ labelTextWhenNoError="Name on bank account"
71
+ errorMessages={nameErrors}
72
+ field={fields.name}
73
+ fieldActions={actions.fields.name}
74
+ showErrors={showErrors}
75
+ onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
76
+ />
77
+ <FormInput
78
+ labelTextWhenNoError="Routing number"
79
+ errorMessages={routingNumberErrors}
80
+ field={fields.routingNumber}
81
+ fieldActions={actions.fields.routingNumber}
82
+ showErrors={showErrors}
83
+ helperModal={() => (
84
+ <ModalWrapper
85
+ tabIndex="0"
86
+ onKeyDown={e =>
87
+ e.key === "Enter" && toggleShowAccount(!showRouting)
88
+ }
89
+ >
90
+ <AccountAndRoutingModal
91
+ link="What is this?"
92
+ title="Where is my routing number?"
93
+ content="Your routing number is the 9-digit number in the bottom left
94
+ corner of your check."
95
+ imageType="Routing"
96
+ isOpen={showRouting}
97
+ toggleOpen={toggleShowRouting}
98
+ />
99
+ </ModalWrapper>
100
+ )}
101
+ onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
102
+ />
103
+ <FormInput
104
+ labelTextWhenNoError="Confirm routing number"
105
+ errorMessages={confirmRoutingNumberErrors}
106
+ field={fields.confirmRoutingNumber}
107
+ fieldActions={actions.fields.confirmRoutingNumber}
108
+ showErrors={showErrors}
109
+ onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
110
+ />
111
+ <FormInput
112
+ labelTextWhenNoError="Account number"
113
+ errorMessages={accountNumberErrors}
114
+ field={fields.accountNumber}
115
+ fieldActions={actions.fields.accountNumber}
116
+ showErrors={showErrors}
117
+ helperModal={() => (
118
+ <ModalWrapper
119
+ tabIndex="0"
120
+ onKeyDown={e =>
121
+ e.key === "Enter" && toggleShowAccount(!showAccount)
122
+ }
123
+ >
124
+ <AccountAndRoutingModal
125
+ link="What is this?"
126
+ title="Where is my account number?"
127
+ content="Your account number is usually the 10-digit number in the bottom
128
+ of your check to the right of the routing number."
129
+ imageType="Account"
130
+ isOpen={showAccount}
131
+ toggleOpen={toggleShowAccount}
132
+ />
133
+ </ModalWrapper>
134
+ )}
135
+ onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
136
+ />
137
+ <FormInput
138
+ labelTextWhenNoError="Confirm account number"
139
+ errorMessages={confirmAccountNumberErrors}
140
+ field={fields.confirmAccountNumber}
141
+ fieldActions={actions.fields.confirmAccountNumber}
142
+ showErrors={showErrors}
143
+ onKeyDown={e => e.key === "Enter" && handleSubmit(e)}
144
+ />
145
+ {allowBankAccountType && (
146
+ <FormSelect
147
+ labelTextWhenNoError="Account type"
148
+ options={[
149
+ { text: "Select account type", value: "" },
150
+ { text: "Checking", value: "CHECKING" },
151
+ { text: "Savings", value: "SAVINGS" }
152
+ ]}
153
+ fieldActions={actions.fields.accountType}
154
+ showErrors={showErrors}
155
+ errorMessages={accountTypeErrors}
156
+ field={fields.accountType}
157
+ />
158
+ )}
159
+ {!hideDefaultPayment && (
160
+ <Checkbox
161
+ title="Save as Default Payment Method"
162
+ name="default-payment-ach"
163
+ onChange={toggleCheckbox}
164
+ checked={defaultMethod.value}
165
+ hidden={hideDefaultPayment}
166
+ />
167
+ )}
168
+ {!!fees?.value && (
169
+ <Alert
170
+ heading="Processing Fee"
171
+ text={`There is a processing fee of ${
172
+ fees.type === "FLAT"
173
+ ? `${displayCurrency(fees.value)}`
174
+ : `${fees.value * 100}%`
175
+ } ${ifElse(
176
+ isNil,
177
+ always(""),
178
+ a => `with a minimum of ${displayCurrency(a)} `
179
+ )(fees.minimumInCents)}on all bank account payments.`}
180
+ variant="info"
181
+ showQuitLink={false}
182
+ />
183
+ )}
184
+ </FormInputColumn>
185
+ </FormContainer>
186
+ );
187
+ };
188
+
189
+ export default PaymentFormACH;