Merge pull request #45 from kjannette/jdurante-1-stripe-paywall

Jdurante 1 stripe paywall
This commit is contained in:
S Jannette
2024-01-26 14:28:22 -06:00
committed by GitHub
18 changed files with 513 additions and 61 deletions

2
.gitignore vendored
View File

@@ -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
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

12
.idea/ax3Client.iml generated Normal file
View 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>

View 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
View 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
View 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
View File

@@ -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",

View File

@@ -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"
}, },

View File

@@ -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);
} }

View File

@@ -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;
await saveUserData(user.uid, dataValues);
navigate("/"); 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) { } 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}
/> />
) : ( ) : (
<></> <></>

View File

@@ -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",
},
}; };

View 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,
},
];

View File

@@ -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",

View File

@@ -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];

View File

@@ -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}
<div></div> onClick={() => !disabled && onClick && onClick(value)}
{rightLabel && <span>{rightLabel}</span>} >
</label> {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> </div>
); );
}; };
export default Radio; export default Radio;
//

View File

@@ -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
View 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;
}

View File

@@ -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;
}