365 lines
10 KiB
JavaScript
365 lines
10 KiB
JavaScript
import React, { useState } from "react";
|
|
import Col from "react-bootstrap/Col";
|
|
import Form from "react-bootstrap/Form";
|
|
import Row from "react-bootstrap/Row";
|
|
import Button from "../../pageElements/Button";
|
|
import { db, auth } from "../../firebase";
|
|
import { createUserWithEmailAndPassword } from "firebase/auth";
|
|
import { Link, useNavigate } from "react-router-dom";
|
|
import TextInput from "../../pageElements/TextInput";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { collection, setDoc, doc } from "firebase/firestore";
|
|
import { splitEvery } from "../../Utils/Array";
|
|
import {
|
|
getFormDataDefaults,
|
|
getValidatedFormData,
|
|
handleFormDataChange,
|
|
isFormDataHasErrors,
|
|
} from "../../Utils/Form";
|
|
import { objectMap } from "../../Utils/Object";
|
|
import { signupFields } from "../../Constants/Fields/SignupFields";
|
|
import { paymentfields } from "../../Constants/Fields/PaymentFields";
|
|
import PaymentModal from "../Modals/PaymentModal";
|
|
import "../../styles/signup.scss";
|
|
import Stripe from 'stripe';
|
|
|
|
const SignupPage = () => {
|
|
const navigate = useNavigate();
|
|
const [notice, setNotice] = useState("");
|
|
const fieldsChunkSize = 2;
|
|
const [isBusy, setIsBusy] = useState(false);
|
|
const [data, setData] = useState(getFormDataDefaults(signupFields));
|
|
const [paymentData, setPaymentData] = useState(
|
|
getFormDataDefaults(paymentfields)
|
|
);
|
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
|
const modalText = "Description of monthly and annual subscriptions";
|
|
|
|
// this could also go in App.js if needed in the future
|
|
const stripe = new Stripe('pk_test_51NNoasBi8p7FeGFrI5SfpM6HuNMoxwImx6NRKyKbgbt6OPxMxQDiZ9I1GqvDa9qUwB3D3jlJOng6MyjPppWofxzP00Exvr8dBy')
|
|
|
|
const apiUrl =
|
|
process.env.NODE_ENV === "development"
|
|
? process.env.REACT_APP_API_DEV
|
|
: process.env.REACT_APP_API_PROD;
|
|
|
|
const handleChangeInput = (e, name) => {
|
|
const newData = handleFormDataChange(e, name, data, signupFields);
|
|
if (newData !== null) {
|
|
setData(newData);
|
|
}
|
|
};
|
|
|
|
const handleChangePaymentInput = (e, name) => {
|
|
const newPaymentData = handleFormDataChange(
|
|
e,
|
|
name,
|
|
paymentData,
|
|
paymentfields
|
|
);
|
|
if (newPaymentData !== null) {
|
|
setPaymentData(newPaymentData);
|
|
}
|
|
};
|
|
|
|
const validateData = () => {
|
|
const newData = getValidatedFormData(data, signupFields);
|
|
const hasErrors = isFormDataHasErrors(newData);
|
|
setData(newData);
|
|
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
|
};
|
|
|
|
const validatePaymentData = () => {
|
|
const newPaymentData = getValidatedFormData(paymentData, paymentfields);
|
|
const hasErrors = isFormDataHasErrors(newPaymentData);
|
|
setPaymentData(newPaymentData);
|
|
return hasErrors ? null : objectMap(({ value }) => value, newPaymentData);
|
|
};
|
|
|
|
async function saveUserData(authId, dataValues) {
|
|
const appUserId = uuidv4();
|
|
const firmId = uuidv4();
|
|
const fbAuthUid = authId;
|
|
const {
|
|
firstName,
|
|
lastName,
|
|
firm,
|
|
telephone,
|
|
streetAddress,
|
|
city,
|
|
state,
|
|
zipCode,
|
|
barNumber,
|
|
practiceArea,
|
|
email,
|
|
} = dataValues;
|
|
|
|
const subscriptionId = dataValues?.subscriptionId;
|
|
const customerId = dataValues?.customerId;
|
|
|
|
const userData = {
|
|
appUserId,
|
|
fbAuthUid,
|
|
firmId,
|
|
firstName,
|
|
lastName,
|
|
firm,
|
|
telephone,
|
|
streetAddress,
|
|
city,
|
|
state,
|
|
zipCode,
|
|
barNumber,
|
|
practiceArea,
|
|
email,
|
|
subscriptionId,
|
|
customerId,
|
|
};
|
|
|
|
try {
|
|
const usersRef = collection(db, "users");
|
|
await setDoc(doc(usersRef, fbAuthUid), userData);
|
|
} catch (error) {
|
|
console.log(`Error saving new user to db: ${error}`);
|
|
}
|
|
}
|
|
|
|
const handleProceedToPayment = (e) => {
|
|
e.preventDefault();
|
|
|
|
if (isBusy) {
|
|
return;
|
|
}
|
|
|
|
const dataValues = validateData();
|
|
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
|
|
|
|
let error = false;
|
|
let subscriptionId = null;
|
|
let customerId = null;
|
|
|
|
const token = await stripe.tokens.create({
|
|
card: {
|
|
number: paymentDataValues.cardNumber,
|
|
exp_month: paymentDataValues.cardExpirationMonth,
|
|
exp_year: paymentDataValues.cardExpirationYear,
|
|
cvc: paymentDataValues.cardCvvCode,
|
|
name: paymentDataValues.cardFirstName + ' ' + paymentDataValues.cardLastName,
|
|
}
|
|
})
|
|
|
|
try {
|
|
let response = await fetch(`${apiUrl}/create-subscription`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'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
|
|
customerData: {
|
|
email: customerDataValues.email,
|
|
name: customerDataValues.firstName + ' ' + customerDataValues.lastName,
|
|
},
|
|
token,
|
|
}),
|
|
})
|
|
|
|
response = await response.json()
|
|
|
|
subscriptionId = response.subscriptionId;
|
|
customerId = response.customerId;
|
|
|
|
console.log('response', response);
|
|
|
|
return {
|
|
subscriptionId,
|
|
customerId,
|
|
}
|
|
}
|
|
catch (error) {
|
|
console.log(error)
|
|
error = true;
|
|
}
|
|
|
|
if(error) {
|
|
return false
|
|
}
|
|
return {
|
|
subscriptionId,
|
|
customerId,
|
|
}
|
|
|
|
return !error;
|
|
}
|
|
|
|
const handleSignup = async (e) => {
|
|
e.preventDefault();
|
|
const paymentDataValues = validatePaymentData();
|
|
if (paymentDataValues === null) {
|
|
return;
|
|
}
|
|
|
|
let dataValues = validateData();
|
|
if (dataValues === null) {
|
|
return;
|
|
}
|
|
|
|
setIsBusy(true);
|
|
|
|
try {
|
|
const userCredential = await createUserWithEmailAndPassword(
|
|
auth,
|
|
dataValues.email,
|
|
dataValues.password
|
|
);
|
|
const user = userCredential.user;
|
|
|
|
const paymentResponse = await handleStripeAuthorization(user, paymentDataValues, dataValues);
|
|
|
|
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(
|
|
error.message || "Sorry, something went wrong. Please try again."
|
|
);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="signup-super-container">
|
|
<div className="signup-sub-container">
|
|
<div className="signup-header">
|
|
<h1 className="signup-header-text">Create a Novodraft account</h1>
|
|
</div>
|
|
<Form className="signup-form">
|
|
{splitEvery(
|
|
Object.keys(signupFields).slice(0, -2),
|
|
fieldsChunkSize
|
|
).map((names, j) => (
|
|
<Row key={`row${j}`}>
|
|
{names.map((name, i) => (
|
|
<Col key={`${name}${i + fieldsChunkSize * j}`} className="mb-3">
|
|
{console.log("data, name", data, name)}
|
|
<TextInput
|
|
name={name}
|
|
value={data[name].value}
|
|
onChange={(e) => handleChangeInput(e, name)}
|
|
error={data[name].error}
|
|
message={data[name].message}
|
|
label={
|
|
data[name].value.length === 0
|
|
? signupFields[name].label
|
|
: ""
|
|
}
|
|
type={signupFields[name].type}
|
|
values={signupFields[name].values}
|
|
disabled={isBusy}
|
|
/>
|
|
</Col>
|
|
))}
|
|
</Row>
|
|
))}
|
|
<Row>
|
|
<Col className="mb-3">
|
|
<TextInput
|
|
id="signupPassword"
|
|
type="password"
|
|
label={
|
|
data.password.value.length === 0
|
|
? signupFields.password.label
|
|
: ""
|
|
}
|
|
value={data.password.value}
|
|
error={data.password.error}
|
|
message={data.password.message}
|
|
onChange={(e) => handleChangeInput(e, "password")}
|
|
disabled={isBusy}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
<Row>
|
|
<Col className="mb-3">
|
|
<TextInput
|
|
id="confirmPassword"
|
|
type="password"
|
|
label={
|
|
data.confirmPassword.value.length === 0
|
|
? signupFields.confirmPassword.label
|
|
: ""
|
|
}
|
|
value={data.confirmPassword.value}
|
|
error={data.confirmPassword.error}
|
|
message={data.confirmPassword.message}
|
|
onChange={(e) => handleChangeInput(e, "confirmPassword")}
|
|
disabled={isBusy}
|
|
/>
|
|
</Col>
|
|
</Row>
|
|
</Form>
|
|
<div className="signup-button-box">
|
|
<Button
|
|
className="primary-button signup-proceed"
|
|
type="button"
|
|
size="lg"
|
|
onClick={handleProceedToPayment}
|
|
disabled={isBusy}
|
|
labelText="Proceed to Payment"
|
|
/>
|
|
<div className="mt-3">
|
|
<span>
|
|
<Link to="/login">Go to login</Link>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{"" !== notice && (
|
|
<>
|
|
<br></br>
|
|
<div className="alert alert-danger" role="alert">
|
|
{notice}
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
{showPaymentModal ? (
|
|
<PaymentModal
|
|
key="modal-one-xc"
|
|
paymentData={paymentData}
|
|
paymentfields={paymentfields}
|
|
setShowPaymentModal={setShowPaymentModal}
|
|
handleChangePaymentInput={handleChangePaymentInput}
|
|
handleSignup={handleSignup}
|
|
modalText={modalText}
|
|
/>
|
|
) : (
|
|
<></>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SignupPage;
|