diff --git a/src/components/contactInfoForm.jsx b/src/components/contactInfoForm.jsx
new file mode 100644
index 0000000..215968d
--- /dev/null
+++ b/src/components/contactInfoForm.jsx
@@ -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 (
+
+
+ Country Code
+
+ 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
+ >
+ India (+91)
+ America (+1)
+
+
+ );
+};
+
+/**
+ * Term checkbox component
+ */
+const TermsCheckbox = ({ acceptTerms, handleAcceptTermsChange }) => {
+ return (
+
+
+ Accept Terms and Conditions
+
+ handleAcceptTermsChange(e.target.checked)}
+ className="mr-2 h-4 w-4 leading-tight"
+ required
+ />
+ I accept the terms and conditions
+
+ );
+};
+
+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 (
+
+
+
+ );
+};
+
+export default contactInfoForm;
diff --git a/src/components/emailAndPassForm.jsx b/src/components/emailAndPassForm.jsx
new file mode 100644
index 0000000..4762dac
--- /dev/null
+++ b/src/components/emailAndPassForm.jsx
@@ -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 (
+
+
+
+ );
+};
+
+export default EmailAndPassForm;
diff --git a/src/components/formButtons.jsx b/src/components/formButtons.jsx
new file mode 100644
index 0000000..f27306e
--- /dev/null
+++ b/src/components/formButtons.jsx
@@ -0,0 +1,40 @@
+const ButtonEle = ({ type = "", buttonLabel, onButtonClick, isDisabled }) => {
+ return (
+ 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}
+
+ );
+};
+
+const FormButtons = ({
+ isBackDisabled,
+ onBack,
+ onSave,
+ onSaveAndNext,
+ isSaveAndNextDisabled,
+ shouldShowSubmit,
+}) => {
+ return (
+
+
+
+
+
+ );
+};
+
+export default FormButtons;
diff --git a/src/components/inputElement.jsx b/src/components/inputElement.jsx
new file mode 100644
index 0000000..c4285f6
--- /dev/null
+++ b/src/components/inputElement.jsx
@@ -0,0 +1,26 @@
+const InputElement = ({
+ labelName,
+ value,
+ onValueChange,
+ type,
+ isValid,
+ errorMsg,
+ isRequired = false,
+}) => {
+ return (
+
+
{labelName}
+
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 &&
{errorMsg}
}
+
+ );
+};
+
+export default InputElement;
diff --git a/src/components/navigationTabs.jsx b/src/components/navigationTabs.jsx
new file mode 100644
index 0000000..730330a
--- /dev/null
+++ b/src/components/navigationTabs.jsx
@@ -0,0 +1,28 @@
+import React, { useState } from "react";
+
+const Tabs = ({ tabs, onTabChange, currentTab }) => {
+ const handleTabClick = (key) => {
+ onTabChange(key);
+ };
+
+ return (
+
+ {tabs.map((tab) => (
+
+ 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}
+
+
+ ))}
+
+ );
+};
+
+export default Tabs;
diff --git a/src/components/personalInfoForm.jsx b/src/components/personalInfoForm.jsx
new file mode 100644
index 0000000..b52cc18
--- /dev/null
+++ b/src/components/personalInfoForm.jsx
@@ -0,0 +1,127 @@
+import { useState, useContext, useMemo } from "react";
+import InputElement from "./inputElement";
+import FormButtons from "./formButtons";
+import { userContext } from "../utils/userContext";
+import { nameRegex } from "../utils/constants";
+
+const PersonalInfoForm = ({ onSave, onSaveAndNext, onBack }) => {
+ const { user, updateUser } = useContext(userContext);
+
+ const [firstName, setFirstName] = useState(user.firstName);
+ const [isFirstNameValid, setIsFirstNameValid] = useState(true);
+
+ const [lastName, setLastName] = useState(user.lastName);
+ const [isLastNameValid, setIsLastNameValid] = useState(true);
+
+ const [address, setAddress] = useState(user.address);
+ const [isAddressValid, setIsAddressValid] = useState(true);
+
+ /**
+ * handles first name validation
+ * @param {string} firstNameVal
+ */
+ const handleFirstNameValidation = (firstNameVal) => {
+ setFirstName(firstNameVal);
+ const isValidNameLength = firstNameVal.length > 1 && firstNameVal.length < 51;
+ setIsFirstNameValid(nameRegex.test(firstNameVal) && isValidNameLength);
+ };
+
+ /**
+ * handles last name validation
+ * @param {string} lastNameVal
+ */
+ const handleLastNameValidation = (lastNameVal) => {
+ setLastName(lastNameVal);
+ setIsLastNameValid(lastNameVal.length == 0 || nameRegex.test(lastNameVal));
+ };
+
+ /**
+ * handles address validation
+ * @param {string} addressVal
+ */
+ const handleAddressValidation = (addressVal) => {
+ setAddress(addressVal);
+ setIsAddressValid(addressVal.length > 9);
+ };
+
+ /**
+ * Flag that indicates whether all inputs provided by user are valid or not
+ */
+ const areAllInputsValid = useMemo(() => {
+ const areAllRequiredValuesProvided = firstName.length && address.length;
+ return areAllRequiredValuesProvided && isFirstNameValid && isLastNameValid && isAddressValid;
+ }, [isFirstNameValid, isAddressValid]);
+
+ /**
+ * Saves user inputs if they are valid and then moves to next form
+ * @param {event} event
+ */
+ const handleSaveAndNext = (event) => {
+ event.preventDefault();
+ if (areAllInputsValid) {
+ handleSave(event);
+ onSaveAndNext(event);
+ return;
+ }
+ if (!isFirstNameValid || !firstName.length) {
+ alert("Please enter valid first name");
+ } else if (!isLastNameValid) {
+ alert("Last name should contain only alphabets");
+ } else if (!isAddressValid || !address.length) {
+ alert("Address must contain atleast 10 characters");
+ }
+ };
+
+ /**
+ * Saves user inputs to react context
+ * @param {event} event
+ */
+ const handleSave = (event) => {
+ event.preventDefault();
+ if (areAllInputsValid) {
+ updateUser({ ...user, firstName, lastName, address });
+ } else {
+ alert("Please enter valid inputs");
+ }
+ };
+
+ return (
+
+
+
+ );
+};
+
+export default PersonalInfoForm;
diff --git a/src/index.css b/src/index.css
index c4110f5..c444b81 100644
--- a/src/index.css
+++ b/src/index.css
@@ -3,3 +3,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+.form-button-disabled:hover {
+ cursor:not-allowed
+}
diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx
index 16836af..278a2ac 100644
--- a/src/pages/Home.jsx
+++ b/src/pages/Home.jsx
@@ -1,28 +1,45 @@
import { Icon } from "@iconify/react";
-import { Link } from "react-router-dom";
+import { useState } from "react";
+import { UserProvider } from "../utils/userContext";
+import PersonalInfoForm from "../components/personalInfoForm";
+import EmailAndPassForm from "../components/emailAndPassForm";
+import ContactInfoForm from "../components/contactInfoForm";
+import Tabs from "../components/navigationTabs";
const Home = () => {
- return (
-
-
-
- Home
-
+ const [activeTab, setActiveTab] = useState(1);
+
+ const tabs = [
+ { key: 1, label: "Email and Password" },
+ { key: 2, label: "Personal Information" },
+ { key: 3, label: "Contact Information" },
+ ];
-
Welcome to the home page!
+ const handleSaveAndNext = (event) => {
+ event.preventDefault();
+ console.log("next");
+ setActiveTab(activeTab + 1);
+ };
-
- Lorem ipsum dolor, sit amet consectetur adipisicing elit. Natus eos quis iure unde incidunt?
- Hic, quisquam. Voluptate placeat officiis corporis dolores ea unde maxime, sed nulla cumque
- amet quam aliquam quas incidunt debitis sit aut a soluta quisquam repellat dignissimos qui.
- Perspiciatis similique quaerat reiciendis nam aliquam?
-
+ const handleBackClick = (event) => {
+ event.preventDefault();
+ setActiveTab(activeTab - 1);
+ };
-
- Posts
-
-
-
+ return (
+
+
+
CodeBuddy Round 2
+
+
+ {activeTab === 1 &&
}
+ {activeTab === 2 && (
+
+ )}
+ {activeTab === 3 &&
}
+
+
+
);
};
diff --git a/src/pages/Posts.jsx b/src/pages/Posts.jsx
index f74e4b3..b1b9910 100644
--- a/src/pages/Posts.jsx
+++ b/src/pages/Posts.jsx
@@ -1,7 +1,34 @@
import { Icon } from "@iconify/react";
import { Link } from "react-router-dom";
+import { useEffect, useState } from "react";
+
+const PostItem = ({ item }) => {
+ const { avatar, firstName, id, image, lastName, writeup } = item;
+ return (
+
+
+
+
{firstName + " " + lastName}
+
+
{id}
+
{writeup}
+
+
+ );
+};
const Posts = () => {
+ const [postArr, setPostArr] = useState([]);
+ async function getPosts() {
+ const data = await fetch("https://codebuddy.review/posts");
+ const json = await data.json();
+ setPostArr(json.data);
+ console.log(json);
+ }
+ useEffect(() => {
+ getPosts();
+ }, []);
+
return (
Posts
@@ -10,31 +37,10 @@ const Posts = () => {
Back to Home
-
-
-
Post 1
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo voluptatem, quibusdam,
- quos, voluptatum voluptas quod quas voluptates quia doloribus nobis voluptatibus. Quam,
- voluptate voluptatum. Quod, voluptate? Quisquam, voluptate voluptatum.
-
-
-
-
Post 2
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo voluptatem, quibusdam,
- quos, voluptatum voluptas quod quas voluptates quia doloribus nobis voluptatibus. Quam,
- voluptate voluptatum. Quod, voluptate? Quisquam, voluptate voluptatum.
-
-
-
-
Post 3
-
- Lorem ipsum dolor sit amet consectetur adipisicing elit. Nemo voluptatem, quibusdam,
- quos, voluptatum voluptas quod quas voluptates quia doloribus nobis voluptatibus. Quam,
- voluptate voluptatum. Quod, voluptate? Quisquam, voluptate voluptatum.
-
-
+
+ {postArr.map((item) => (
+
+ ))}
);
diff --git a/src/utils/constants.jsx b/src/utils/constants.jsx
new file mode 100644
index 0000000..5a8dedb
--- /dev/null
+++ b/src/utils/constants.jsx
@@ -0,0 +1,7 @@
+export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+export const passwordRegex =
+ /^(?=(?:[^A-Z]*[A-Z]){2})(?=(?:[^a-z]*[a-z]){2})(?=(?:[^\d]*\d){2})(?=(?:[^\W_]*\W){2}).{8,}$/;
+
+export const nameRegex = /^[a-zA-Z]+$/;
+
+export const mobileNumberRegex = /^\d{10}$/;
diff --git a/src/utils/userContext.jsx b/src/utils/userContext.jsx
new file mode 100644
index 0000000..619fe6c
--- /dev/null
+++ b/src/utils/userContext.jsx
@@ -0,0 +1,25 @@
+import { createContext, useState } from "react";
+
+const userContext = createContext();
+
+const UserProvider = ({ children }) => {
+ const [user, setUser] = useState({
+ emailId: "",
+ password: "",
+ firstName: "",
+ lastName: "",
+ address: "",
+ countryCode: "+91",
+ phoneNumber: "",
+ });
+
+ const updateUser = (newUser) => {
+ setUser(() => ({
+ ...newUser,
+ }));
+ };
+
+ return
{children} ;
+};
+
+export { userContext, UserProvider };