Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d3872ac

Browse files
authored
#278 | Membership Payment Form Redesign
* Merge branch feature/payment-form into master
1 parent a6a75f5 commit d3872ac

File tree

10 files changed

+868
-264
lines changed

10 files changed

+868
-264
lines changed

apps/web/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
"react-router-dom": "^5.1.2",
2323
"react-scripts": "^3.4.1",
2424
"react-scroll": "^1.7.12",
25-
"react-stripe-elements": "^5.0.0",
25+
"react-stripe-elements": "^6.1.2",
2626
"react-toastify": "^5.4.0",
2727
"styled-components": "^4.4.0"
2828
},
@@ -49,7 +49,7 @@
4949
"@types/react-modal": "^3.8.3",
5050
"@types/react-router-dom": "^5.1.0",
5151
"@types/react-scroll": "^1.5.4",
52-
"@types/react-stripe-elements": "^1.3.4",
52+
"@types/react-stripe-elements": "^6.0.4",
5353
"@types/styled-components": "4.1.8",
5454
"@typescript-eslint/eslint-plugin": "^2.3.3",
5555
"@typescript-eslint/parser": "^2.3.3",

apps/web/src/components/CheckoutForm.tsx

Lines changed: 97 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import Icon from "react-eva-icons";
66

77
import { ExecutionResult } from "@apollo/react-common";
88
import {
9-
CardElement,
109
Elements,
1110
injectStripe,
1211
ReactStripeElements
@@ -18,8 +17,10 @@ import {
1817
MembershipTypes,
1918
useGetMembershipMutation
2019
} from "../generated/graphql";
20+
import { PaymentInformationForm } from "./PaymentInformation";
21+
import { ReviewInformationForm } from "./ReviewInformation";
2122
import { IconContainer } from "./IconContainer";
22-
import { PrimaryButton } from "./PrimaryButton";
23+
import { ModalWrapper } from "./ModalWrapper";
2324

2425
export const GET_MEMBERSHIP: any = gql`
2526
mutation GetMembership($membershipType: MembershipTypes!) {
@@ -31,176 +32,102 @@ export const GET_MEMBERSHIP: any = gql`
3132
}
3233
`;
3334

34-
const createOptions = (fontSize: string, padding?: string) => {
35-
return {
36-
style: {
37-
base: {
38-
fontSize,
39-
borderRadius: "20px",
40-
width: "100%",
41-
color: "#424770",
42-
letterSpacing: "0.025em",
43-
fontFamily: "Source Code Pro, monospace",
44-
"::placeholder": {
45-
color: "#aab7c4"
46-
},
47-
padding
48-
},
49-
invalid: {
50-
color: "#9e2146"
51-
}
52-
}
53-
};
54-
};
55-
56-
const FormContainer: AnyStyledComponent = styled.div`
57-
display: flex;
58-
flex-direction: column;
59-
align-content: center;
60-
justify-content: center;
61-
align-items: center;
62-
63-
margin: 3rem 1rem 0 1rem;
64-
`;
65-
66-
const ElementContainer: AnyStyledComponent = styled.div`
67-
width: 100%;
68-
input,
69-
.StripeElement {
70-
display: block;
71-
margin: 10px 0 20px 0;
72-
max-width: 500px;
73-
padding: 8px 12px;
74-
font-size: 1em;
75-
font-family: "Source Code Pro", monospace;
76-
box-shadow: rgba(50, 50, 93, 0.14902) 0px 1px 3px,
77-
rgba(0, 0, 0, 0.0196078) 0px 1px 0px;
78-
border: 0;
79-
outline: 0;
80-
border-radius: 0.5rem;
81-
background: white;
82-
}
83-
`;
84-
8535
const ErrorContainer: AnyStyledComponent = styled.div`
8636
display: flex;
8737
align-items: center;
8838
align-content: center;
39+
margin-bottom: .5rem;
8940
9041
color: #f56565;
9142
`;
9243

9344
interface ICheckoutFormProps {
94-
tag: MembershipTypes;
9545
onSuccess?: () => void;
46+
removeTag: () => void;
47+
tag: MembershipTypes;
9648
}
9749

98-
type CheckoutProps = ReactStripeElements.InjectedStripeProps &
50+
type CheckoutFormProps = ReactStripeElements.InjectedStripeProps &
9951
ICheckoutFormProps;
10052

10153
type setString = React.Dispatch<React.SetStateAction<string>>;
102-
type setBoolean = React.Dispatch<React.SetStateAction<boolean>>;
54+
type setNumber = React.Dispatch<React.SetStateAction<number>>;
55+
type setPaymentMethod = React.Dispatch<React.SetStateAction<stripe.paymentMethod.PaymentMethod | undefined>>;
56+
type voidFunction = () => void;
57+
type asyncVoidFunction = () => Promise<void>;
10358

104-
const CheckoutFormBase: React.FC<CheckoutProps> = (
105-
props: CheckoutProps
59+
const CheckoutFormBase: React.FC<CheckoutFormProps> = (
60+
props: CheckoutFormProps
10661
): JSX.Element => {
107-
const [cardElement, setCardElement] = useState<any>(undefined);
108-
const [intent, setIntent]: [string, setString] = useState<string>("");
109-
const [clientSecret, setClientSecret]: [string, setString] = useState<string>(
110-
""
111-
);
62+
// const [intent, setIntent]: [string, setString] = useState<string>("");
63+
const [paymentMethod, setPaymentMethod]: [stripe.paymentMethod.PaymentMethod | undefined, setPaymentMethod] = useState<stripe.paymentMethod.PaymentMethod | undefined>(undefined);
64+
const [clientSecret, setClientSecret]: [string, setString] = useState<string>("");
11265
const [error, setError]: [string, setString] = useState<string>("");
113-
const [success, setSuccess]: [boolean, setBoolean] = useState<boolean>(false);
114-
const [loading, setLoading]: [boolean, setBoolean] = useState<boolean>(false);
66+
const [index, setIndex]: [number, setNumber] = useState<number>(0);
11567

11668
const [getMembership] = useGetMembershipMutation();
11769

11870
const handleError: (message: string) => void = (message: string): void => {
119-
setLoading(false);
71+
setPaymentMethod(undefined);
12072
setError(message);
73+
setIndex(0);
12174
};
12275

123-
const handleSubmit: (ev: React.FormEvent) => void = async (
124-
ev: React.FormEvent
125-
): Promise<void> => {
126-
// We don't want to let default form submission happen here, which would refresh the page.
127-
ev.preventDefault();
128-
setLoading(true);
129-
if (!props.stripe) {
130-
handleError("Stripe.js hasn't loaded yet.");
131-
return;
132-
}
133-
let secret: string = clientSecret;
134-
135-
if (!secret) {
136-
let result: ExecutionResult<GetMembershipMutation>;
137-
try {
138-
result = await getMembership({
139-
variables: { membershipType: props.tag }
140-
});
141-
} catch (e) {
142-
handleError(e.message);
143-
return;
144-
}
145-
146-
const data = result.data;
147-
148-
if (!data) {
149-
if (result.errors) {
150-
handleError(result.errors[0].message || "Unknown Error occurred.");
151-
}
152-
153-
return;
154-
}
155-
secret = data.startMembershipTransaction.clientSecret;
156-
setClientSecret(secret);
157-
}
76+
const resetForm: asyncVoidFunction = async (): Promise<void> => {
77+
props.removeTag();
78+
setPaymentMethod(undefined);
79+
setClientSecret("");
80+
setTimeout(() => {
81+
setIndex(0);
82+
setError("");
83+
}, 400);
84+
}
15885

159-
const response: stripe.PaymentIntentResponse = await props.stripe.handleCardPayment(
160-
secret,
161-
cardElement
162-
);
86+
const nextModal: voidFunction = (): void => {
87+
setIndex(index + 1);
88+
}
16389

164-
if (response.error) {
165-
handleError(response.error.message || "Unknown error occurred");
90+
const prevModal: voidFunction = (): void => {
91+
setIndex(index - 1);
92+
}
16693

167-
return;
94+
const getClientSecret: () => Promise<string> = async (): Promise<string> => {
95+
let result: ExecutionResult<GetMembershipMutation>;
96+
try {
97+
result = await getMembership({
98+
variables: { membershipType: props.tag }
99+
});
100+
} catch (e) {
101+
handleError(e.message);
102+
return "";
168103
}
104+
const data = result.data;
169105

170-
if (!response.paymentIntent) {
171-
handleError("Unknown error occurred");
106+
if (!data) {
107+
if (result.errors)
108+
handleError(result.errors[0].message || "Unknown Error occurred.");
172109

173-
return;
110+
return "";
174111
}
175-
176-
setIntent(response.paymentIntent.id);
177-
setLoading(false);
178-
setSuccess(true);
112+
const clientSecret = data.startMembershipTransaction.clientSecret;
113+
setClientSecret(clientSecret);
114+
return clientSecret;
179115
};
180116

181-
if (success) {
182-
if (props.onSuccess) {
183-
props.onSuccess();
184-
}
185-
186-
return (
187-
<Result
188-
status="success"
189-
title="Membership Added!"
190-
subTitle={`Your membership may take 1-5 minutes to show. ref: ${intent}`}
191-
/>
192-
);
193-
}
194-
195-
return (
196-
<form onSubmit={handleSubmit}>
197-
<FormContainer>
198-
<label style={{ width: "100%", fontSize: "16px" }}>
199-
Card details
200-
<ElementContainer>
201-
<CardElement {...createOptions("18px")} onReady={setCardElement} />
202-
</ElementContainer>
203-
</label>
117+
const slides = [
118+
<ModalWrapper
119+
resetForm={resetForm}
120+
tag={props.tag}
121+
>
122+
<PaymentInformationForm
123+
stripe={props.stripe}
124+
setPaymentMethod={setPaymentMethod}
125+
getClientSecret={getClientSecret}
126+
handleError={handleError}
127+
resetForm={resetForm}
128+
nextModal={nextModal}
129+
clientSecret={clientSecret}
130+
>
204131
{error !== "" && (
205132
<ErrorContainer>
206133
<IconContainer>
@@ -213,19 +140,41 @@ const CheckoutFormBase: React.FC<CheckoutProps> = (
213140
<span style={{ marginLeft: ".5rem" }}>{error}</span>
214141
</ErrorContainer>
215142
)}
216-
<PrimaryButton
217-
loading={loading}
218-
disabled={loading}
219-
style={{ margin: "2rem auto" }}
220-
>
221-
Purchase
222-
</PrimaryButton>
223-
</FormContainer>
224-
</form>
143+
</PaymentInformationForm>
144+
</ModalWrapper>,
145+
<ModalWrapper
146+
resetForm={resetForm}
147+
tag={props.tag}
148+
>
149+
<ReviewInformationForm
150+
stripe={props.stripe}
151+
handleError={handleError}
152+
// setIntent={setIntent}
153+
nextModal={nextModal}
154+
prevModal={prevModal}
155+
paymentMethod={paymentMethod!}
156+
clientSecret={clientSecret}
157+
tag={props.tag}
158+
/>
159+
</ModalWrapper>,
160+
<ModalWrapper
161+
resetForm={resetForm}
162+
tag={props.tag}
163+
>
164+
<Result
165+
status="success"
166+
title="Membership Added!"
167+
subTitle={`Your membership may take 1-5 minutes to show.`}
168+
/>
169+
</ModalWrapper>
170+
]
171+
172+
return (
173+
slides[index]
225174
);
226175
};
227176

228-
const InjectedCheckoutForm: React.ComponentType<CheckoutProps> = injectStripe(
177+
const InjectedCheckoutForm: React.ComponentType<ICheckoutFormProps> = injectStripe(
229178
CheckoutFormBase
230179
);
231180

0 commit comments

Comments
 (0)