@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.
- package/.github/workflows/bump-version.yml +30 -0
- package/.github/workflows/create-release/build-body.sh +35 -0
- package/.github/workflows/create-release.yml +52 -0
- package/.github/workflows/disabled-workflows/publish-update.yml +73 -0
- package/README.md +68 -90
- package/dist/index.cjs.js +31180 -3325
- package/package.json +15 -35
- package/rollup.config.js +3 -1
- package/src/components/atoms/breadcrumb/Breadcrumb.js +0 -2
- package/src/components/atoms/button-with-action/ButtonWithAction.js +30 -4
- package/src/components/atoms/button-with-action/ButtonWithAction.theme.js +64 -234
- package/src/components/atoms/formatted-credit-card/FormattedCreditCard.js +53 -0
- package/src/components/atoms/formatted-credit-card/FormattedCreditCard.theme.js +9 -0
- package/src/components/atoms/formatted-credit-card/index.js +3 -0
- package/src/components/atoms/icons/AccountNumberImage.js +95 -0
- package/src/components/atoms/icons/BankIcon.js +82 -0
- package/src/components/atoms/icons/CheckmarkIcon.js +55 -0
- package/src/components/atoms/icons/GenericCard.js +39 -0
- package/src/components/atoms/icons/PaymentIcon.js +50 -0
- package/src/components/atoms/icons/RoutingNumberImage.js +95 -0
- package/src/components/atoms/icons/index.js +14 -1
- package/src/components/atoms/index.js +3 -0
- package/src/components/atoms/jumbo/Jumbo.js +76 -0
- package/src/components/atoms/jumbo/index.js +3 -0
- package/src/components/atoms/layouts/Box.js +0 -2
- package/src/components/atoms/layouts/Box.styled.js +1 -17
- package/src/components/atoms/layouts/Motion.styled.js +2 -5
- package/src/components/atoms/link/ExternalLink.js +3 -3
- package/src/components/atoms/link/ExternalLink.styled.js +9 -2
- package/src/components/atoms/link/InternalLink.js +2 -4
- package/src/components/atoms/link/InternalLink.styled.js +13 -15
- package/src/components/atoms/link/Link.theme.js +7 -1
- package/src/components/atoms/loading/Loading.js +17 -0
- package/src/components/atoms/loading/index.js +3 -0
- package/src/components/atoms/nav-header/NavHeader.js +1 -1
- package/src/components/atoms/placeholder/Placeholder.js +2 -1
- package/src/components/atoms/text/Text.js +0 -2
- package/src/components/atoms/text/Text.styled.js +2 -8
- package/src/components/atoms/toggle-switch/ToggleSwitch.js +1 -1
- package/src/components/index.js +1 -0
- package/src/components/molecules/account-and-routing-modal/AccountAndRoutingModal.js +74 -0
- package/src/components/molecules/account-and-routing-modal/AccountAndRoutingModal.theme.js +24 -0
- package/src/components/molecules/account-and-routing-modal/index.js +3 -0
- package/src/components/molecules/address-form/AddressForm.js +2 -1
- package/src/components/molecules/address-form/index.js +6 -6
- package/src/components/molecules/change-password-form/ChangePasswordForm.js +2 -1
- package/src/components/molecules/change-password-form/index.js +1 -1
- package/src/components/molecules/collapsible-section/CollapsibleSection.js +1 -1
- package/src/components/molecules/edit-name-form/EditNameForm.js +2 -1
- package/src/components/molecules/edit-name-form/index.js +1 -1
- package/src/components/molecules/editable-list/EditableList.js +139 -0
- package/src/components/molecules/editable-list/EditableList.styled.js +31 -0
- package/src/components/molecules/editable-list/index.js +3 -0
- package/src/components/molecules/editable-table/EditableTable.js +30 -0
- package/src/components/molecules/editable-table/EditableTable.styled.js +80 -0
- package/src/components/molecules/editable-table/TableListItem.js +64 -0
- package/src/components/molecules/editable-table/index.js +4 -0
- package/src/components/molecules/email-form/EmailForm.js +2 -1
- package/src/components/molecules/email-form/index.js +1 -1
- package/src/components/molecules/forgot-password-form/ForgotPasswordForm.js +2 -1
- package/src/components/molecules/forgot-password-form/index.js +1 -1
- package/src/components/molecules/index.js +5 -0
- package/src/components/molecules/login-form/LoginForm.js +2 -1
- package/src/components/molecules/login-form/index.js +1 -1
- package/src/components/molecules/module/Module.js +1 -3
- package/src/components/molecules/partial-amount-form/PartialAmountForm.js +73 -0
- package/src/components/molecules/partial-amount-form/PartialAmountForm.state.js +51 -0
- package/src/components/molecules/partial-amount-form/index.js +4 -0
- package/src/components/molecules/payment-form-ach/PaymentFormACH.js +189 -0
- package/src/components/molecules/payment-form-ach/PaymentFormACH.state.js +38 -0
- package/src/components/molecules/payment-form-ach/index.js +11 -0
- package/src/components/molecules/payment-form-card/PaymentFormCard.js +132 -0
- package/src/components/molecules/payment-form-card/PaymentFormCard.state.js +39 -0
- package/src/components/molecules/payment-form-card/index.js +11 -0
- package/src/components/molecules/phone-form/PhoneForm.js +2 -1
- package/src/components/molecules/phone-form/index.js +1 -1
- package/src/components/molecules/radio-section/RadioSection.js +1 -1
- package/src/components/molecules/registration-form/RegistrationForm.js +2 -1
- package/src/components/molecules/registration-form/index.js +1 -1
- package/src/components/molecules/reset-password-form/ResetPasswordForm.js +3 -1
- package/src/components/molecules/reset-password-form/index.js +1 -1
- package/src/components/molecules/tab-sidebar/TabSidebar.js +10 -5
- package/src/components/molecules/terms-and-conditions-modal/TermsAndConditionsModal.js +0 -1
- package/src/constants/index.js +4 -0
- package/src/index.js +3 -1
- package/src/util/formats.js +54 -2
- package/src/util/general.js +27 -4
- package/src/util/index.js +4 -0
- package/src/util/inputValidationUtils.js +0 -167
- 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,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;
|
|
@@ -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(), []);
|
|
@@ -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,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(), []);
|
|
@@ -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,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;
|