Merge branch 'jdurante-1-stripe-paywall' of github.com:kjannette/ax3Client into jdurante-1-stripe-paywall
This commit is contained in:
@@ -19,9 +19,11 @@ import {
|
||||
import { objectMap } from "../../Utils/Object";
|
||||
import { signupFields } from "../../Constants/Fields/SignupFields";
|
||||
import { paymentfields } from "../../Constants/Fields/PaymentFields";
|
||||
import { signupRadioFields } from "../../Constants/Fields/RadioFields";
|
||||
import PaymentModal from "../Modals/PaymentModal";
|
||||
import Radio from "../../pageElements/Radio";
|
||||
import "../../styles/signup.scss";
|
||||
import Stripe from 'stripe';
|
||||
import Stripe from "stripe";
|
||||
|
||||
const SignupPage = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -33,10 +35,14 @@ const SignupPage = () => {
|
||||
getFormDataDefaults(paymentfields)
|
||||
);
|
||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||
const [showSelectPlan, setShowSelectPlan] = useState(true);
|
||||
const [activeRadioOption, setActiveRadioOption] = useState("monthlyBasic");
|
||||
const modalText = "Description of monthly and annual subscriptions";
|
||||
|
||||
const [selectedPlan, setSelectedPlan] = useState({});
|
||||
// this could also go in App.js if needed in the future
|
||||
const stripe = new Stripe('pk_test_51NNoasBi8p7FeGFrI5SfpM6HuNMoxwImx6NRKyKbgbt6OPxMxQDiZ9I1GqvDa9qUwB3D3jlJOng6MyjPppWofxzP00Exvr8dBy')
|
||||
const stripe = new Stripe(
|
||||
"pk_test_51NNoasBi8p7FeGFrI5SfpM6HuNMoxwImx6NRKyKbgbt6OPxMxQDiZ9I1GqvDa9qUwB3D3jlJOng6MyjPppWofxzP00Exvr8dBy"
|
||||
);
|
||||
|
||||
const apiUrl =
|
||||
process.env.NODE_ENV === "development"
|
||||
@@ -62,6 +68,20 @@ const SignupPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeRadioInput = (value) => {
|
||||
const tempPlan = signupRadioFields.filter((field) => {
|
||||
return field.value === value;
|
||||
});
|
||||
console.log("tempPlan", tempPlan);
|
||||
setSelectedPlan(tempPlan);
|
||||
const temp = value;
|
||||
setActiveRadioOption(temp);
|
||||
};
|
||||
|
||||
signupRadioFields.map((option) => {
|
||||
console.log(option.value);
|
||||
});
|
||||
|
||||
const validateData = () => {
|
||||
const newData = getValidatedFormData(data, signupFields);
|
||||
const hasErrors = isFormDataHasErrors(newData);
|
||||
@@ -127,6 +147,11 @@ const SignupPage = () => {
|
||||
const handleProceedToPayment = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
setShowPaymentModal(true);
|
||||
};
|
||||
|
||||
const handleOpenSelectPlan = (e) => {
|
||||
e.preventDefault();
|
||||
if (isBusy) {
|
||||
return;
|
||||
}
|
||||
@@ -135,15 +160,12 @@ const SignupPage = () => {
|
||||
if (dataValues === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setShowPaymentModal(true);
|
||||
};
|
||||
|
||||
async function handleStripeAuthorization(user, paymentDataValues, customerDataValues) {
|
||||
// Do: calls to Stripe API
|
||||
// if success, return some truthy value or
|
||||
// handle failure in handleSignup
|
||||
|
||||
async function handleStripeAuthorization(
|
||||
user,
|
||||
paymentDataValues,
|
||||
customerDataValues
|
||||
) {
|
||||
let error = false;
|
||||
let subscriptionId = null;
|
||||
let customerId = null;
|
||||
@@ -154,52 +176,53 @@ const SignupPage = () => {
|
||||
exp_month: paymentDataValues.cardExpirationMonth,
|
||||
exp_year: paymentDataValues.cardExpirationYear,
|
||||
cvc: paymentDataValues.cardCvvCode,
|
||||
name: paymentDataValues.cardFirstName + ' ' + paymentDataValues.cardLastName,
|
||||
}
|
||||
})
|
||||
name:
|
||||
paymentDataValues.cardFirstName +
|
||||
" " +
|
||||
paymentDataValues.cardLastName,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
let response = await fetch(`${apiUrl}/create-subscription`, {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: 'monthly', // TODO: replace this with the user-chosen value later when we have the UI for it
|
||||
type: "monthly", // TODO: replace this with the user-chosen value later when we have the UI for it
|
||||
customerData: {
|
||||
email: customerDataValues.email,
|
||||
name: customerDataValues.firstName + ' ' + customerDataValues.lastName,
|
||||
name:
|
||||
customerDataValues.firstName + " " + customerDataValues.lastName,
|
||||
},
|
||||
token,
|
||||
}),
|
||||
})
|
||||
});
|
||||
|
||||
response = await response.json()
|
||||
response = await response.json();
|
||||
|
||||
subscriptionId = response.subscriptionId;
|
||||
customerId = response.customerId;
|
||||
|
||||
console.log('response', response);
|
||||
console.log("response", response);
|
||||
|
||||
return {
|
||||
subscriptionId,
|
||||
customerId,
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.log(error)
|
||||
};
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
error = true;
|
||||
}
|
||||
|
||||
if(error) {
|
||||
return false
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
subscriptionId,
|
||||
customerId,
|
||||
}
|
||||
|
||||
return !error;
|
||||
};
|
||||
}
|
||||
|
||||
const handleSignup = async (e) => {
|
||||
@@ -216,38 +239,43 @@ const SignupPage = () => {
|
||||
|
||||
setIsBusy(true);
|
||||
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
dataValues.email,
|
||||
dataValues.password
|
||||
);
|
||||
const user = userCredential.user;
|
||||
if (paymentDataValues && dataValues) {
|
||||
try {
|
||||
const userCredential = await createUserWithEmailAndPassword(
|
||||
auth,
|
||||
dataValues.email,
|
||||
dataValues.password
|
||||
);
|
||||
const user = userCredential.user;
|
||||
|
||||
const paymentResponse = await handleStripeAuthorization(user, paymentDataValues, dataValues);
|
||||
const paymentResponse = await handleStripeAuthorization(
|
||||
user,
|
||||
paymentDataValues,
|
||||
dataValues
|
||||
);
|
||||
|
||||
if(paymentResponse === false) {
|
||||
// handle payment failure
|
||||
if (paymentResponse === false) {
|
||||
// handle payment failure
|
||||
setIsBusy(false);
|
||||
setNotice("Sorry, something went wrong. Please try again.");
|
||||
return;
|
||||
} else {
|
||||
dataValues = { ...dataValues, ...paymentResponse };
|
||||
|
||||
await saveUserData(user.uid, dataValues);
|
||||
navigate("/");
|
||||
}
|
||||
} catch (error) {
|
||||
setIsBusy(false);
|
||||
setNotice(
|
||||
"Sorry, something went wrong. Please try again."
|
||||
error.message || "Sorry, something went wrong. Please try again."
|
||||
);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
dataValues = {...dataValues, ...paymentResponse};
|
||||
|
||||
await saveUserData(user.uid, dataValues);
|
||||
navigate("/");
|
||||
}
|
||||
} catch (error) {
|
||||
setIsBusy(false);
|
||||
setNotice(
|
||||
error.message || "Sorry, something went wrong. Please try again."
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const showNoticeOnPage = "" !== notice && !showPaymentModal;
|
||||
|
||||
return (
|
||||
<div className="signup-super-container">
|
||||
<div className="signup-sub-container">
|
||||
@@ -281,7 +309,7 @@ const SignupPage = () => {
|
||||
))}
|
||||
</Row>
|
||||
))}
|
||||
<Row>
|
||||
<Row style={{ width: "80%" }}>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
id="signupPassword"
|
||||
@@ -299,7 +327,7 @@ const SignupPage = () => {
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Row style={{ width: "80%" }}>
|
||||
<Col className="mb-3">
|
||||
<TextInput
|
||||
id="confirmPassword"
|
||||
@@ -323,26 +351,68 @@ const SignupPage = () => {
|
||||
className="primary-button signup-proceed"
|
||||
type="button"
|
||||
size="lg"
|
||||
onClick={handleProceedToPayment}
|
||||
onClick={handleOpenSelectPlan}
|
||||
disabled={isBusy}
|
||||
labelText="Proceed to Payment"
|
||||
labelText="Select A Plan"
|
||||
/>
|
||||
<div className="mt-3">
|
||||
<span>
|
||||
<Link to="/login">Go to login</Link>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{"" !== notice && (
|
||||
{showNoticeOnPage ? (
|
||||
<>
|
||||
<br></br>
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{notice}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
{showSelectPlan ? (
|
||||
<div className="select-plan-container">
|
||||
<div className="select-sub-left">
|
||||
<div>
|
||||
<h5 className="signup-subheader-text">
|
||||
Select your subscription plan
|
||||
</h5>
|
||||
</div>
|
||||
<div className="radio-group-box">
|
||||
{signupRadioFields?.map((option, i) => (
|
||||
<Radio
|
||||
onClick={handleChangeRadioInput}
|
||||
value={option.value}
|
||||
name={option.name}
|
||||
description={option.description}
|
||||
details={option.details}
|
||||
activeRadioOption={activeRadioOption}
|
||||
price={option.price}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="select-sub-right">
|
||||
<div className="select-plan-summary-box ">
|
||||
<div>Order summary</div>
|
||||
<div>{`{subVar} subscription type`}</div>
|
||||
<div>{`{subVar} subscription mini description`}</div>
|
||||
<div>{`Total billed today: {subVar} `}</div>
|
||||
<div>Plus aplicable sales tax -toooltip</div>
|
||||
</div>
|
||||
<div className="selectplan-payment-button-wrapper">
|
||||
<Button
|
||||
className="primary-button signup-proceed"
|
||||
type="button"
|
||||
size="lg"
|
||||
onClick={handleProceedToPayment}
|
||||
disabled={isBusy}
|
||||
labelText="Proceed to Payment"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{showPaymentModal ? (
|
||||
<PaymentModal
|
||||
key="modal-one-xc"
|
||||
@@ -352,6 +422,8 @@ const SignupPage = () => {
|
||||
handleChangePaymentInput={handleChangePaymentInput}
|
||||
handleSignup={handleSignup}
|
||||
modalText={modalText}
|
||||
notice={notice}
|
||||
setNotice={setNotice}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
|
||||
26
src/Constants/Fields/RadioFields.js
Normal file
26
src/Constants/Fields/RadioFields.js
Normal file
@@ -0,0 +1,26 @@
|
||||
export const signupRadioFields = [
|
||||
{
|
||||
name: "Monthly - basic",
|
||||
label: "Monthly - basic",
|
||||
value: "monthlyBasic",
|
||||
description: "Generate 1 document per month",
|
||||
details: "Billed every month",
|
||||
price: 89,
|
||||
},
|
||||
{
|
||||
name: "Monthly - pro",
|
||||
label: "Monthly - pro",
|
||||
value: "monthlyProPlan",
|
||||
description: "Generate up to 3 documents per month",
|
||||
details: "Billed every three months. A xxx percent savings",
|
||||
price: 159,
|
||||
},
|
||||
{
|
||||
name: "Annual - unlimted annual",
|
||||
label: "Annual - unlimited annual",
|
||||
value: "annualUnlimited",
|
||||
description: "Generate unlimited documents. Billed annualy",
|
||||
details: "Billed annualy. A xxx percent savings",
|
||||
price: 1489,
|
||||
},
|
||||
];
|
||||
@@ -31,7 +31,13 @@ export const signupFields = {
|
||||
},
|
||||
barNumber: { required: true, label: "Bar Number", maxLength: 45 },
|
||||
practiceArea: { required: true, label: "Practice Area", maxLength: 45 },
|
||||
email: { required: true, label: "Email", maxLength: 45, type: "email" },
|
||||
email: {
|
||||
required: true,
|
||||
label: "Email",
|
||||
maxLength: 45,
|
||||
type: "email",
|
||||
inputWidth: "80%",
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
label: "Password",
|
||||
|
||||
@@ -84,6 +84,11 @@ export const getValidatedFormData = (data, fields) =>
|
||||
}, data);
|
||||
|
||||
export const handleFormDataChange = (e, name, data, fields) => {
|
||||
console.log("fields", fields);
|
||||
if (!fields) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value =
|
||||
(typeof e?.target?.value === "undefined" ? e.value : e.target.value) || "";
|
||||
const field = fields[name];
|
||||
|
||||
@@ -1,23 +1,73 @@
|
||||
import { CircleFill, Circle } from "react-bootstrap-icons";
|
||||
import "../styles/radio.scss";
|
||||
|
||||
const Radio = ({
|
||||
rightLabel,
|
||||
leftLabel,
|
||||
containerClassName,
|
||||
labelClassName,
|
||||
...props
|
||||
}) => {
|
||||
const Radio = (props) => {
|
||||
console.log("hello");
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
onClick,
|
||||
value,
|
||||
price,
|
||||
disabled,
|
||||
activeRadioOption,
|
||||
details,
|
||||
} = props;
|
||||
const classCheckbox = "checkbox";
|
||||
const classOption = "option";
|
||||
console.log("value, activeRadioOption", value, activeRadioOption);
|
||||
return (
|
||||
<div className={containerClassName}>
|
||||
<label className={labelClassName}>
|
||||
<input {...props} type="radio" />
|
||||
{leftLabel && <span>{leftLabel}</span>}
|
||||
<div></div>
|
||||
{rightLabel && <span>{rightLabel}</span>}
|
||||
</label>
|
||||
<div>
|
||||
<div
|
||||
className="radio-option-container"
|
||||
key={name}
|
||||
onClick={() => !disabled && onClick && onClick(value)}
|
||||
>
|
||||
{value == activeRadioOption ? (
|
||||
<>
|
||||
<div className="radio-circle-container">
|
||||
<div className="radio-circle-top">
|
||||
<div className="radio-circle-box">
|
||||
<CircleFill color="#1a76c7" size="20px" />
|
||||
</div>
|
||||
<div className="radio-name-box"> {`${name}`} </div>
|
||||
<div className="radio-price-box">
|
||||
<div className="radio-price"> {`${price}`} </div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div></div>
|
||||
<div className="radio-circle-lower">
|
||||
<div className="radio-description-box">
|
||||
{" "}
|
||||
{`${description}`}{" "}
|
||||
</div>
|
||||
<div className="radio-description-box">{`${details}`} </div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="radio-circle-container">
|
||||
<div className="radio-circle-top">
|
||||
<div className="radio-circle-box">
|
||||
<Circle color="#1a76c7" size="20px" />
|
||||
</div>
|
||||
<div className="radio-name-box">{`${name}`} </div>
|
||||
<div className="radio-price-box">
|
||||
<div className="radio-price"> {`${price}`} </div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="radio-circle-lower">
|
||||
<div className="radio-description-box">{`${description}`} </div>
|
||||
<div className="radio-description-box">{`${details}`} </div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Radio;
|
||||
|
||||
//
|
||||
|
||||
@@ -11,7 +11,7 @@ const Radiogroup = (props) => {
|
||||
onClick,
|
||||
value,
|
||||
options,
|
||||
disabled
|
||||
disabled,
|
||||
} = props;
|
||||
const classCheckbox = "checkbox";
|
||||
const classOption = "option";
|
||||
@@ -90,7 +90,3 @@ const Radiogroup = (props) => {
|
||||
*/
|
||||
|
||||
export default Radiogroup;
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
49
src/styles/radio.scss
Normal file
49
src/styles/radio.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
.radio-circle-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #fff;
|
||||
height: 160px;
|
||||
margin: 10px 8px 0px 8px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #d4d4d4;
|
||||
}
|
||||
|
||||
.radio-circle-top {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.radio-circle-lower {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 18px;
|
||||
padding-left: 26px;
|
||||
}
|
||||
|
||||
.radio-circle-box {
|
||||
height: 40px;
|
||||
margin: 12px 8px;
|
||||
}
|
||||
|
||||
.radio-name-box {
|
||||
height: 40px;
|
||||
width: 50%;
|
||||
font-family: var(--main-font);
|
||||
font-family: Roboto;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.05rem;
|
||||
margin: 13px 8px;
|
||||
}
|
||||
|
||||
.radio-price-box {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin: 16px 8px;
|
||||
width: 30%;
|
||||
}
|
||||
|
||||
.radio-price {
|
||||
width: 20px;
|
||||
float: right;
|
||||
}
|
||||
@@ -32,12 +32,21 @@
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.signup-subheader-text {
|
||||
letter-spacing: -0.6px;
|
||||
font-weight: 400;
|
||||
font-size: 1.7rem;
|
||||
margin-left: 5px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.signup-form {
|
||||
width: 880px;
|
||||
}
|
||||
|
||||
.signup-button-box {
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
@@ -46,3 +55,70 @@
|
||||
.signup-proceed {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.select-plan-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 24px;
|
||||
width: 920px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.select-sub-left {
|
||||
width: 60%;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(240, 247, 250);
|
||||
margin-right: 8px;
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.select-sub-right {
|
||||
width: 40%;
|
||||
border-radius: 10px;
|
||||
background-color: rgb(240, 247, 250);
|
||||
margin-left: 8px;
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.signup-radio-wrapper {
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.signup-option-div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.radio-group-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 6px;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.select-plan-summary-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 250px;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.selectplan-payment-button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
margin: 6px;
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.signup-option {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user