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 ( +
+ + +
+ ); +}; + +/** + * Term checkbox component + */ +const TermsCheckbox = ({ acceptTerms, handleAcceptTermsChange }) => { + return ( +
+ + 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 ( + + ); +}; + +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 ( +
+ + 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 ( + + ); +}; + +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 };