Merge pull request #45 from kjannette/jdurante-1-stripe-paywall
Jdurante 1 stripe paywall
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -4,7 +4,7 @@ build/
|
|||||||
dist/
|
dist/
|
||||||
*.pyc
|
*.pyc
|
||||||
*.pyo
|
*.pyo
|
||||||
**/env/
|
.env/
|
||||||
env/
|
env/
|
||||||
.env
|
.env
|
||||||
mCovo.py
|
mCovo.py
|
||||||
|
|||||||
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
12
.idea/ax3Client.iml
generated
Normal file
12
.idea/ax3Client.iml
generated
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ax3Client.iml" filepath="$PROJECT_DIR$/.idea/ax3Client.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
29
package-lock.json
generated
29
package-lock.json
generated
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "client",
|
"name": "novodraft-client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "client",
|
"name": "novodraft-client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@testing-library/jest-dom": "^5.17.0",
|
"@testing-library/jest-dom": "^5.17.0",
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"docx": "^8.2.4",
|
"docx": "^8.2.4",
|
||||||
|
"dotenv": "^16.3.2",
|
||||||
"firebase": "^10.3.1",
|
"firebase": "^10.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.8.0",
|
"react-bootstrap": "^2.8.0",
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-spinners": "^0.13.8",
|
"react-spinners": "^0.13.8",
|
||||||
|
"stripe": "^14.13.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
@@ -7849,6 +7851,17 @@
|
|||||||
"tslib": "^2.0.3"
|
"tslib": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz",
|
||||||
|
"integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dotenv-expand": {
|
"node_modules/dotenv-expand": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
|
||||||
@@ -17275,6 +17288,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/stripe": {
|
||||||
|
"version": "14.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/stripe/-/stripe-14.13.0.tgz",
|
||||||
|
"integrity": "sha512-uLOfWtBUL1amJCTpKCXWrHntFOSaO2PWb/2hsxV/OlXLr0bz5MyU8IW1pFlmZqpw6hBqAW5Fad7Ty7xRxDYrzA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": ">=8.1.0",
|
||||||
|
"qs": "^6.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/style-loader": {
|
"node_modules/style-loader": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"bootstrap": "^5.3.2",
|
"bootstrap": "^5.3.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"docx": "^8.2.4",
|
"docx": "^8.2.4",
|
||||||
|
"dotenv": "^16.3.2",
|
||||||
"firebase": "^10.3.1",
|
"firebase": "^10.3.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-bootstrap": "^2.8.0",
|
"react-bootstrap": "^2.8.0",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"react-spinners": "^0.13.8",
|
"react-spinners": "^0.13.8",
|
||||||
|
"stripe": "^14.13.0",
|
||||||
"uuid": "^9.0.1",
|
"uuid": "^9.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ const AccountPage = () => {
|
|||||||
const [docsAllowed, setDocsAllowed] = useState(10);
|
const [docsAllowed, setDocsAllowed] = useState(10);
|
||||||
const appUserId = group ? group.appUserId : null;
|
const appUserId = group ? group.appUserId : null;
|
||||||
const [showModal, setShowModal] = useState(false);
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
|
||||||
|
const apiUrl =
|
||||||
|
process.env.NODE_ENV === "development"
|
||||||
|
? process.env.REACT_APP_API_DEV
|
||||||
|
: process.env.REACT_APP_API_PROD;
|
||||||
|
|
||||||
function getDocumentsCount() {
|
function getDocumentsCount() {
|
||||||
if (!appUserId) {
|
if (!appUserId) {
|
||||||
return;
|
return;
|
||||||
@@ -37,10 +43,18 @@ const AccountPage = () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancelSub = () => {
|
const handleCancelSub = async () => {
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
try {
|
try {
|
||||||
//API call(s)
|
let response = await fetch(`${apiUrl}/cancel-subscription`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
appUserId: appUserId,
|
||||||
|
}),
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,25 +17,40 @@ import {
|
|||||||
isFormDataHasErrors,
|
isFormDataHasErrors,
|
||||||
} from "../../Utils/Form";
|
} from "../../Utils/Form";
|
||||||
import { objectMap } from "../../Utils/Object";
|
import { objectMap } from "../../Utils/Object";
|
||||||
import { signupfields } from "../../Constants/Fields/SignupFields";
|
import { signupFields } from "../../Constants/Fields/SignupFields";
|
||||||
import { paymentfields } from "../../Constants/Fields/PaymentFields";
|
import { paymentfields } from "../../Constants/Fields/PaymentFields";
|
||||||
|
import { signupRadioFields } from "../../Constants/Fields/RadioFields";
|
||||||
import PaymentModal from "../Modals/PaymentModal";
|
import PaymentModal from "../Modals/PaymentModal";
|
||||||
|
import Radio from "../../pageElements/Radio";
|
||||||
import "../../styles/signup.scss";
|
import "../../styles/signup.scss";
|
||||||
|
import Stripe from "stripe";
|
||||||
|
|
||||||
const SignupPage = () => {
|
const SignupPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [notice, setNotice] = useState("");
|
const [notice, setNotice] = useState("");
|
||||||
const fieldsChunkSize = 2;
|
const fieldsChunkSize = 2;
|
||||||
const [isBusy, setIsBusy] = useState(false);
|
const [isBusy, setIsBusy] = useState(false);
|
||||||
const [data, setData] = useState(getFormDataDefaults(signupfields));
|
const [data, setData] = useState(getFormDataDefaults(signupFields));
|
||||||
const [paymentData, setPaymentData] = useState(
|
const [paymentData, setPaymentData] = useState(
|
||||||
getFormDataDefaults(paymentfields)
|
getFormDataDefaults(paymentfields)
|
||||||
);
|
);
|
||||||
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
const [showPaymentModal, setShowPaymentModal] = useState(false);
|
||||||
|
const [showSelectPlan, setShowSelectPlan] = useState(true);
|
||||||
|
const [activeRadioOption, setActiveRadioOption] = useState("monthlyBasic");
|
||||||
const modalText = "Description of monthly and annual subscriptions";
|
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 apiUrl =
|
||||||
|
process.env.NODE_ENV === "development"
|
||||||
|
? process.env.REACT_APP_API_DEV
|
||||||
|
: process.env.REACT_APP_API_PROD;
|
||||||
|
|
||||||
const handleChangeInput = (e, name) => {
|
const handleChangeInput = (e, name) => {
|
||||||
const newData = handleFormDataChange(e, name, data, signupfields);
|
const newData = handleFormDataChange(e, name, data, signupFields);
|
||||||
if (newData !== null) {
|
if (newData !== null) {
|
||||||
setData(newData);
|
setData(newData);
|
||||||
}
|
}
|
||||||
@@ -53,8 +68,22 @@ 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 validateData = () => {
|
||||||
const newData = getValidatedFormData(data, signupfields);
|
const newData = getValidatedFormData(data, signupFields);
|
||||||
const hasErrors = isFormDataHasErrors(newData);
|
const hasErrors = isFormDataHasErrors(newData);
|
||||||
setData(newData);
|
setData(newData);
|
||||||
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
return hasErrors ? null : objectMap(({ value }) => value, newData);
|
||||||
@@ -85,6 +114,9 @@ const SignupPage = () => {
|
|||||||
email,
|
email,
|
||||||
} = dataValues;
|
} = dataValues;
|
||||||
|
|
||||||
|
const subscriptionId = dataValues?.subscriptionId;
|
||||||
|
const customerId = dataValues?.customerId;
|
||||||
|
|
||||||
const userData = {
|
const userData = {
|
||||||
appUserId,
|
appUserId,
|
||||||
fbAuthUid,
|
fbAuthUid,
|
||||||
@@ -100,6 +132,8 @@ const SignupPage = () => {
|
|||||||
barNumber,
|
barNumber,
|
||||||
practiceArea,
|
practiceArea,
|
||||||
email,
|
email,
|
||||||
|
subscriptionId,
|
||||||
|
customerId,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -113,6 +147,11 @@ const SignupPage = () => {
|
|||||||
const handleProceedToPayment = (e) => {
|
const handleProceedToPayment = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
|
setShowPaymentModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpenSelectPlan = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
if (isBusy) {
|
if (isBusy) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -121,14 +160,69 @@ const SignupPage = () => {
|
|||||||
if (dataValues === null) {
|
if (dataValues === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setShowPaymentModal(true);
|
|
||||||
};
|
};
|
||||||
|
async function handleStripeAuthorization(
|
||||||
|
user,
|
||||||
|
paymentDataValues,
|
||||||
|
customerDataValues
|
||||||
|
) {
|
||||||
|
let error = false;
|
||||||
|
let subscriptionId = null;
|
||||||
|
let customerId = null;
|
||||||
|
|
||||||
async function handleStripeAuthorization(paymentDataValues) {
|
const token = await stripe.tokens.create({
|
||||||
// Do: calls to Stripe API
|
card: {
|
||||||
// if success, return some truthy value or
|
number: paymentDataValues.cardNumber,
|
||||||
// handle failure in handleSignup
|
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,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSignup = async (e) => {
|
const handleSignup = async (e) => {
|
||||||
@@ -138,14 +232,14 @@ const SignupPage = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dataValues = validateData();
|
let dataValues = validateData();
|
||||||
if (dataValues === null) {
|
if (dataValues === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsBusy(true);
|
setIsBusy(true);
|
||||||
const paymentSuccess = handleStripeAuthorization(paymentDataValues);
|
|
||||||
if (paymentSuccess) {
|
if (paymentDataValues && dataValues) {
|
||||||
try {
|
try {
|
||||||
const userCredential = await createUserWithEmailAndPassword(
|
const userCredential = await createUserWithEmailAndPassword(
|
||||||
auth,
|
auth,
|
||||||
@@ -153,19 +247,35 @@ const SignupPage = () => {
|
|||||||
dataValues.password
|
dataValues.password
|
||||||
);
|
);
|
||||||
const user = userCredential.user;
|
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);
|
await saveUserData(user.uid, dataValues);
|
||||||
navigate("/");
|
navigate("/");
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setIsBusy(false);
|
setIsBusy(false);
|
||||||
setNotice(
|
setNotice(
|
||||||
error.message || "Sorry, something went wrong. Please try again."
|
error.message || "Sorry, something went wrong. Please try again."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//handle payment failure
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const showNoticeOnPage = "" !== notice && !showPaymentModal;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="signup-super-container">
|
<div className="signup-super-container">
|
||||||
<div className="signup-sub-container">
|
<div className="signup-sub-container">
|
||||||
@@ -174,7 +284,7 @@ const SignupPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
<Form className="signup-form">
|
<Form className="signup-form">
|
||||||
{splitEvery(
|
{splitEvery(
|
||||||
Object.keys(signupfields).slice(0, -2),
|
Object.keys(signupFields).slice(0, -2),
|
||||||
fieldsChunkSize
|
fieldsChunkSize
|
||||||
).map((names, j) => (
|
).map((names, j) => (
|
||||||
<Row key={`row${j}`}>
|
<Row key={`row${j}`}>
|
||||||
@@ -188,25 +298,25 @@ const SignupPage = () => {
|
|||||||
message={data[name].message}
|
message={data[name].message}
|
||||||
label={
|
label={
|
||||||
data[name].value.length === 0
|
data[name].value.length === 0
|
||||||
? signupfields[name].label
|
? signupFields[name].label
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
type={signupfields[name].type}
|
type={signupFields[name].type}
|
||||||
values={signupfields[name].values}
|
values={signupFields[name].values}
|
||||||
disabled={isBusy}
|
disabled={isBusy}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
))}
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
))}
|
))}
|
||||||
<Row>
|
<Row style={{ width: "80%" }}>
|
||||||
<Col className="mb-3">
|
<Col className="mb-3">
|
||||||
<TextInput
|
<TextInput
|
||||||
id="signupPassword"
|
id="signupPassword"
|
||||||
type="password"
|
type="password"
|
||||||
label={
|
label={
|
||||||
data.password.value.length === 0
|
data.password.value.length === 0
|
||||||
? signupfields.password.label
|
? signupFields.password.label
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
value={data.password.value}
|
value={data.password.value}
|
||||||
@@ -217,14 +327,14 @@ const SignupPage = () => {
|
|||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
<Row style={{ width: "80%" }}>
|
||||||
<Col className="mb-3">
|
<Col className="mb-3">
|
||||||
<TextInput
|
<TextInput
|
||||||
id="confirmPassword"
|
id="confirmPassword"
|
||||||
type="password"
|
type="password"
|
||||||
label={
|
label={
|
||||||
data.confirmPassword.value.length === 0
|
data.confirmPassword.value.length === 0
|
||||||
? signupfields.confirmPassword.label
|
? signupFields.confirmPassword.label
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
value={data.confirmPassword.value}
|
value={data.confirmPassword.value}
|
||||||
@@ -241,26 +351,68 @@ const SignupPage = () => {
|
|||||||
className="primary-button signup-proceed"
|
className="primary-button signup-proceed"
|
||||||
type="button"
|
type="button"
|
||||||
size="lg"
|
size="lg"
|
||||||
onClick={handleProceedToPayment}
|
onClick={handleOpenSelectPlan}
|
||||||
disabled={isBusy}
|
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>
|
</div>
|
||||||
|
|
||||||
{"" !== notice && (
|
{showNoticeOnPage ? (
|
||||||
<>
|
<>
|
||||||
<br></br>
|
<br></br>
|
||||||
<div className="alert alert-danger" role="alert">
|
<div className="alert alert-danger" role="alert">
|
||||||
{notice}
|
{notice}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
)}
|
)}
|
||||||
</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 ? (
|
{showPaymentModal ? (
|
||||||
<PaymentModal
|
<PaymentModal
|
||||||
key="modal-one-xc"
|
key="modal-one-xc"
|
||||||
@@ -270,6 +422,8 @@ const SignupPage = () => {
|
|||||||
handleChangePaymentInput={handleChangePaymentInput}
|
handleChangePaymentInput={handleChangePaymentInput}
|
||||||
handleSignup={handleSignup}
|
handleSignup={handleSignup}
|
||||||
modalText={modalText}
|
modalText={modalText}
|
||||||
|
notice={notice}
|
||||||
|
setNotice={setNotice}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
|
|||||||
@@ -23,4 +23,16 @@ export const paymentfields = {
|
|||||||
maxLength: 5,
|
maxLength: 5,
|
||||||
type: "cardCvvCode",
|
type: "cardCvvCode",
|
||||||
},
|
},
|
||||||
|
cardExpirationMonth: {
|
||||||
|
required: true,
|
||||||
|
label: "Card Expiration Month",
|
||||||
|
maxLength: 2,
|
||||||
|
type: "cardExpirationMonth",
|
||||||
|
},
|
||||||
|
cardExpirationYear: {
|
||||||
|
required: true,
|
||||||
|
label: "Card Expiration Year",
|
||||||
|
maxLength: 4,
|
||||||
|
type: "cardExpirationYear",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
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.",
|
||||||
|
details: "Billed annualy. A xxx percent savings",
|
||||||
|
price: 1489,
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export const signupfields = {
|
export const signupFields = {
|
||||||
firstName: { required: true, label: "First Name", maxLength: 45 },
|
firstName: { required: true, label: "First Name", maxLength: 45 },
|
||||||
lastName: { required: true, label: "Last Name", maxLength: 45 },
|
lastName: { required: true, label: "Last Name", maxLength: 45 },
|
||||||
firm: { required: true, label: "Firm", minLength: 2, maxLength: 45 },
|
firm: { required: true, label: "Firm", minLength: 2, maxLength: 45 },
|
||||||
@@ -31,7 +31,13 @@ export const signupfields = {
|
|||||||
},
|
},
|
||||||
barNumber: { required: true, label: "Bar Number", maxLength: 45 },
|
barNumber: { required: true, label: "Bar Number", maxLength: 45 },
|
||||||
practiceArea: { required: true, label: "Practice Area", 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: {
|
password: {
|
||||||
required: true,
|
required: true,
|
||||||
label: "Password",
|
label: "Password",
|
||||||
@@ -84,6 +84,11 @@ export const getValidatedFormData = (data, fields) =>
|
|||||||
}, data);
|
}, data);
|
||||||
|
|
||||||
export const handleFormDataChange = (e, name, data, fields) => {
|
export const handleFormDataChange = (e, name, data, fields) => {
|
||||||
|
console.log("fields", fields);
|
||||||
|
if (!fields) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const value =
|
const value =
|
||||||
(typeof e?.target?.value === "undefined" ? e.value : e.target.value) || "";
|
(typeof e?.target?.value === "undefined" ? e.value : e.target.value) || "";
|
||||||
const field = fields[name];
|
const field = fields[name];
|
||||||
|
|||||||
@@ -1,23 +1,73 @@
|
|||||||
|
import { CircleFill, Circle } from "react-bootstrap-icons";
|
||||||
|
import "../styles/radio.scss";
|
||||||
|
|
||||||
const Radio = ({
|
const Radio = (props) => {
|
||||||
rightLabel,
|
console.log("hello");
|
||||||
leftLabel,
|
const {
|
||||||
containerClassName,
|
name,
|
||||||
labelClassName,
|
description,
|
||||||
...props
|
onClick,
|
||||||
}) => {
|
value,
|
||||||
|
price,
|
||||||
|
disabled,
|
||||||
|
activeRadioOption,
|
||||||
|
details,
|
||||||
|
} = props;
|
||||||
|
const classCheckbox = "checkbox";
|
||||||
|
const classOption = "option";
|
||||||
|
console.log("value, activeRadioOption", value, activeRadioOption);
|
||||||
return (
|
return (
|
||||||
<div className={containerClassName}>
|
<div>
|
||||||
<label className={labelClassName}>
|
<div
|
||||||
<input {...props} type="radio" />
|
className="radio-option-container"
|
||||||
{leftLabel && <span>{leftLabel}</span>}
|
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></div>
|
||||||
{rightLabel && <span>{rightLabel}</span>}
|
<div className="radio-circle-lower">
|
||||||
</label>
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Radio;
|
export default Radio;
|
||||||
|
|
||||||
//
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Radiogroup = (props) => {
|
|||||||
onClick,
|
onClick,
|
||||||
value,
|
value,
|
||||||
options,
|
options,
|
||||||
disabled
|
disabled,
|
||||||
} = props;
|
} = props;
|
||||||
const classCheckbox = "checkbox";
|
const classCheckbox = "checkbox";
|
||||||
const classOption = "option";
|
const classOption = "option";
|
||||||
@@ -90,7 +90,3 @@ const Radiogroup = (props) => {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export default Radiogroup;
|
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;
|
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 {
|
.signup-form {
|
||||||
width: 880px;
|
width: 880px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signup-button-box {
|
.signup-button-box {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
margin-bottom: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@@ -46,3 +55,70 @@
|
|||||||
.signup-proceed {
|
.signup-proceed {
|
||||||
width: 160px;
|
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