Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 166 additions & 0 deletions src/components/contactInfoForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import InputElement from "./inputElement";
import FormButtons from "./formButtons";
import { useState, useContext, useMemo } from "react";
import { userContext } from "../utils/userContext";
import { mobileNumberRegex } from "../utils/constants";

/**
* Country code dropdown component
*/
const CountryCodeDropdown = ({ value, onChange }) => {
return (
<div className="mb-4">
<label htmlFor="countryCode" className="mb-2 block font-semibold text-gray-700">
Country Code
</label>
<select
value={value}
onChange={(event) => onChange(event.target.value)}
className="w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm focus:border-blue-500 focus:outline-none"
required
>
<option value="+91">India (+91)</option>
<option value="+1">America (+1)</option>
</select>
</div>
);
};

/**
* Term checkbox component
*/
const TermsCheckbox = ({ acceptTerms, handleAcceptTermsChange }) => {
return (
<div className="mb-4">
<label htmlFor="acceptTerms" className="mb-2 block font-semibold text-gray-700">
Accept Terms and Conditions
</label>
<input
type="checkbox"
id="acceptTerms"
name="acceptTerms"
checked={acceptTerms}
onChange={(e) => handleAcceptTermsChange(e.target.checked)}
className="mr-2 h-4 w-4 leading-tight"
required
/>
<span className="text-gray-700">I accept the terms and conditions</span>
</div>
);
};

const contactInfoForm = ({ onBack, history }) => {
const { user, updateUser } = useContext(userContext);
const [phoneNo, setPhoneNo] = useState(user.phoneNumber);
const [isPhoneNoValid, setIsPhoneNoValid] = useState(true);

const [countryCode, setCountryCode] = useState(user.countryCode);
const [termsFlag, setTermsFlag] = useState(false);

/**
* handles phone number validation
* @param contactNo
*/
const handlePhoneNoValidation = (contactNo) => {
setPhoneNo(contactNo);
setIsPhoneNoValid(mobileNumberRegex.test(contactNo));
};

/**
* Submits the user data to backend API and redirects to posts route
*/
async function addUsertoDb() {
try {
const response = await fetch("https://codebuddy.review/submit", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ ...user }),
});

if (!response.ok) {
throw new Error("Network response was not ok");
}

const result = await response.json();
console.log(result);
window.location.href = "/posts";
} catch (error) {
throw new Error("error while adding new user");
}
}

/**
* Submits user data to backend
* @param {event} event
*/
const handleSubmit = (event) => {
event.preventDefault();
if (areAllInputsValid) {
addUsertoDb();
}
};

/**
* Flag that indicates whether all inputs provided by user are valid or not
*/
const areAllInputsValid = useMemo(() => {
const areAllRequiredValuesProvided = phoneNo.length && countryCode;
return areAllRequiredValuesProvided && isPhoneNoValid && countryCode.length && termsFlag;
}, [isPhoneNoValid, countryCode, termsFlag]);

/**
* Returns true when all user data (including all 3 forms) is valid.
* @returns {Bool}isValid
*/
const isValidUSer = () => {
const { emailId, password, firstName, address } = user;
return emailId.length && password.length && firstName.length && address.length;
};

/**
* Saves data to react context
* @param {event} event
*/
const handleSave = (event) => {
event.preventDefault();
if (areAllInputsValid) {
updateUser({ ...user, countryCode, phoneNumber: phoneNo });
if (isValidUSer()) {
addUsertoDb();
} else {
alert("please fill out all the forms");
}
} else if (!isPhoneNoValid || !phoneNo.length) {
alert("Please enter valid phone number");
} else if (!countryCode.length) {
alert("Please select a country code");
} else if (!termsFlag) {
alert("Please accept terms and conditions!!!");
} else {
alert("please fill out all the forms");
}
};

return (
<div className="w-full max-w-md rounded-b-lg bg-white p-8 shadow-md">
<form onSubmit={handleSubmit}>
<CountryCodeDropdown value={countryCode} onChange={setCountryCode} />
<InputElement
labelName="Phone Number"
type="tel"
value={phoneNo}
onValueChange={handlePhoneNoValidation}
isValid={isPhoneNoValid}
isRequired={true}
errorMsg={isPhoneNoValid ? "" : "Enter valid phone number"}
/>
<TermsCheckbox acceptTerms={termsFlag} handleAcceptTermsChange={setTermsFlag} />
<FormButtons onBack={onBack} onSave={handleSave} isSaveAndNextDisabled={true} />
</form>
</div>
);
};

export default contactInfoForm;
108 changes: 108 additions & 0 deletions src/components/emailAndPassForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { useState, useMemo, useContext } from "react";
import InputElement from "./inputElement";
import FormButtons from "./formButtons";
import { userContext } from "../utils/userContext";
import { emailRegex, passwordRegex } from "../utils/constants";

const EmailAndPassForm = ({ onSave, onSaveAndNext, onBack }) => {
const { user, updateUser } = useContext(userContext);

const [email, setEmail] = useState(user.emailId);
const [pass, setPass] = useState(user.password);

const [isMailValid, setIsMailValid] = useState(true);
const [isPassValid, setIsPassValid] = useState(true);

/**
* handles email validation
* @param {string} emailVal
*/
const handleEmailValidation = (emailVal) => {
const isValid = emailRegex.test(String(emailVal).toLowerCase());
setEmail(emailVal);
setIsMailValid(isValid);
};

/**
* handles password validation
* @param {string} passwordVal
*/
const handlePasswordValidation = (passwordVal) => {
setPass(passwordVal);
const isValid = passwordRegex.test(String(passwordVal));
setIsPassValid(isValid);
};

/**
* Flag that indicates whether all inputs provided by user are valid or not
*/
const areAllInputsValid = useMemo(() => {
const areAllRequiredValuesProvided = email.length && pass.length;
return areAllRequiredValuesProvided && isMailValid && isPassValid;
}, [isMailValid, isPassValid]);

/**
* Saves user inputs to react context if they are valid and then moves to next form
* @param {event} event
*/
const handleSaveAndNext = (event) => {
event.preventDefault();
console.log(areAllInputsValid, "areAllInputsValid");
if (areAllInputsValid) {
handleSave(event);
onSaveAndNext(event);
return;
}
if (!isMailValid || !email.length) {
alert("please enter valid email address");
} else if (!isPassValid || !pass.length) {
alert("please enter valid password");
}
};

/**
* Saves user inputs to react context
* @param {event} event
*/
const handleSave = (event) => {
event.preventDefault();
if (areAllInputsValid) {
updateUser({ ...user, emailId: email, password: pass });
} else {
alert("Please enter valid inputs");
}
console.log(user);
};

return (
<div className="w-full max-w-md rounded-b-lg bg-white p-8 shadow-md">
<form>
<InputElement
labelName="Email"
type="email"
value={email}
onValueChange={handleEmailValidation}
isValid={isMailValid}
errorMsg={isMailValid ? "" : "Please enter valid Email Id"}
isRequired={true}
/>
<InputElement
labelName="Password"
type="password"
value={pass}
onValueChange={handlePasswordValidation}
isValid={isPassValid}
errorMsg={
isPassValid
? ""
: "Your password must contain atleast 2 capital letters, 2 small letter, 2 numbers and 2 special characters."
}
isRequired={true}
/>
<FormButtons onSave={handleSave} onSaveAndNext={handleSaveAndNext} isBackDisabled={true} />
</form>
</div>
);
};

export default EmailAndPassForm;
40 changes: 40 additions & 0 deletions src/components/formButtons.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const ButtonEle = ({ type = "", buttonLabel, onButtonClick, isDisabled }) => {
return (
<button
type={type}
onClick={(e) => onButtonClick(e)}
disabled={isDisabled}
className={`rounded-lg px-4 py-2 text-white shadow
${
isDisabled
? "form-button-disabled bg-blue-300"
: "bg-blue-500 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50"
}`}
>
{buttonLabel}
</button>
);
};

const FormButtons = ({
isBackDisabled,
onBack,
onSave,
onSaveAndNext,
isSaveAndNextDisabled,
shouldShowSubmit,
}) => {
return (
<div className="flex w-fit gap-2">
<ButtonEle buttonLabel="Back" isDisabled={isBackDisabled} onButtonClick={onBack} />
<ButtonEle buttonLabel="Save" onButtonClick={onSave} />
<ButtonEle
buttonLabel="Save and Next"
onButtonClick={onSaveAndNext}
isDisabled={isSaveAndNextDisabled}
/>
</div>
);
};

export default FormButtons;
26 changes: 26 additions & 0 deletions src/components/inputElement.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const InputElement = ({
labelName,
value,
onValueChange,
type,
isValid,
errorMsg,
isRequired = false,
}) => {
return (
<div className="mb-4">
<label className="mb-2 block font-semibold text-gray-700">{labelName}</label>
<input
type={type}
value={value}
onChange={(e) => onValueChange(e.target.value)}
className={`w-full rounded-lg border border-gray-300 px-3 py-2 shadow-sm
${isValid ? "border-gray-300 focus:border-blue-500 focus:outline-none" : "border-red-500"}`}
required={isRequired}
/>
{!isValid && <p className="text-xs text-red-600">{errorMsg}</p>}
</div>
);
};

export default InputElement;
28 changes: 28 additions & 0 deletions src/components/navigationTabs.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React, { useState } from "react";

const Tabs = ({ tabs, onTabChange, currentTab }) => {
const handleTabClick = (key) => {
onTabChange(key);
};

return (
<ul className="flex w-full max-w-md rounded-t-lg bg-white pt-2">
{tabs.map((tab) => (
<li key={tab.key} className={`${currentTab === tab.key ? "border-blue-500" : ""}`}>
<button
onClick={() => handleTabClick(tab.key)}
className={`inline-block px-4 py-2 ${
currentTab === tab.key
? "bg-white font-semibold text-blue-500"
: "bg-gray-200 text-gray-500"
}`}
>
{tab.label}
</button>
</li>
))}
</ul>
);
};

export default Tabs;
Loading