diff --git a/frontend/public/images/testimonialImg.png b/frontend/public/images/testimonialImg.png new file mode 100644 index 00000000..d3766ab0 Binary files /dev/null and b/frontend/public/images/testimonialImg.png differ diff --git a/frontend/src/pages/Admin/Admin.jsx b/frontend/src/pages/Admin/Admin.jsx index b6e3b157..312af9f5 100644 --- a/frontend/src/pages/Admin/Admin.jsx +++ b/frontend/src/pages/Admin/Admin.jsx @@ -25,6 +25,9 @@ import { useDispatch } from "react-redux"; import { ManageFaq } from "./Components/Faq/ManageFaq"; import { QandA } from "./Components/Faq/Q&A/QandA"; import { Manageqa } from "./Components/Faq/Q&A/ManageQ&A/ManageQ&A"; +import { Testimonial } from "./Components/Testimonial"; +import { AddTestimonial } from "./Components/Testimonial/AddTestimonial"; +import { ManageTestimonial } from "./Components/Testimonial/ManageTestimonial"; export const Admin = (props) => { const [tab, setTab] = useState(1); @@ -177,6 +180,20 @@ export const Admin = (props) => {
FAQs and Q&As
+
  • +
    setTab(20)} + > + +
    Testimonial
    +
    +
  • { ) : tab === 19 ? ( + ) : tab === 20 ? ( + + ) : tab === 21 ? ( + + ) : tab === 22 ? ( + ) : null}
    diff --git a/frontend/src/pages/Admin/Components/Dashboard/Dashboard.jsx b/frontend/src/pages/Admin/Components/Dashboard/Dashboard.jsx index 58f74a6d..fde38f5b 100644 --- a/frontend/src/pages/Admin/Components/Dashboard/Dashboard.jsx +++ b/frontend/src/pages/Admin/Components/Dashboard/Dashboard.jsx @@ -4,6 +4,7 @@ import { useSelector } from "react-redux"; import LiveHelpIcon from "@material-ui/icons/LiveHelp"; import PermContactCalendarIcon from "@material-ui/icons/PermContactCalendar"; import { SimpleToast } from "../../../../components/util/Toast"; +import Chat from "@material-ui/icons/Chat" export const Dashboard = (props) => { const [openLoginSuccess, setOpenLoginSuccessToast] = React.useState(false); @@ -50,6 +51,11 @@ export const Dashboard = (props) => { icon: , tab: 5, }, + { + name: "Testimonial", + icon: , + tab: 20, + }, ]; return ( diff --git a/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/AddTestimonial.jsx b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/AddTestimonial.jsx new file mode 100644 index 00000000..76fc7105 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/AddTestimonial.jsx @@ -0,0 +1,278 @@ +import React, { useState } from "react"; +import Joi from "joi-browser"; +import styles from "./add-testimonial.module.scss"; +import { Button2 } from "../../../../../components/util/Button/index"; +import { Grid } from "@material-ui/core"; +import { SimpleToast } from "./../../../../../components/util/Toast/Toast"; +import { addTestimonial } from "../../../../../service/Testimonial"; + +export function AddTestimonial() { + const [formData, setFormData] = useState({ + name: "", + position: "", + company: "", + image: "https://i.pinimg.com/originals/f4/cd/d8/f4cdd85c50e44aa59a303fb163ff90f8.jpg", + text: "", + rating: "", + }); + const [toast, setToast] = useState({ + toastStatus: false, + toastType: "", + toastMessage: "", + }); + const [formErrors, setFormErrors] = useState({}); + const [picUrl, setPicUrl] = useState("./images/testimonialImg.png"); + const [pic, setPic] = useState(); + const schema = { + name: Joi.string().required().label("Name"), + position: Joi.string().required().label("Position"), + company: Joi.string().required().label("Company"), + image: Joi.any().required().label("Image"), + text: Joi.string().required().label("Text"), + rating: Joi.number().required().min(1).max(5).label("Rating"), + }; + + const validate = () => { + const result = Joi.validate(formData, schema, { abortEarly: false }); + if (!result.error) return {}; + const errors = {}; + for (let item of result.error.details) { + errors[item.path[0]] = item.message; + } + return errors; + }; + + const validateProperty = (input) => { + const { name, value } = input; + const obj = { [name]: value }; + const obj_schema = { [name]: schema[name] }; + const result = Joi.validate(obj, obj_schema); + return result.error ? result.error.details[0].message : null; + }; + const onPicChange = (event) => { + const { target } = event; + const { files } = target; + + if (files && files[0]) { + setPic(files[0]); + let reader = new FileReader(); + reader.onload = function (e) { + setPicUrl(e.target.result); + }; + reader.readAsDataURL(files[0]); + } + return; + }; + + const changePic = () => { + return document.getElementById("profile-pic-input")?.click(); + }; + + const handleCloseToast = (event, reason) => { + if (reason === "clickaway") { + return; + } + setToast({ ...toast, toastStatus: false }); + }; + + const handleChange = (e) => { + const { currentTarget: input } = e; + const errors = { ...formErrors }; + const errorMessage = validateProperty(input); + if (errorMessage) errors[input.name] = errorMessage; + else delete errors[input.name]; + + const data = { ...formData }; + data[input.name] = input.value; + setFormData(data); + setFormErrors(errors); + }; + + const onSubmit = async (e) => { + e.preventDefault(); + const errors = validate(); + setFormErrors(errors); + if (Object.keys(errors).length !== 0) { + console.log(errors); + } else { + // Call the server + await addTestimonial(formData, setToast, toast); + + const temp = { + name: "", + position: "", + company: "", + text: "", + rating: "", + }; + setFormData(temp); + setPicUrl("./images/testimonialImg.png"); + } + return pic; + }; + + return ( +
    +
    +
    +
    +

    + Add Testimonial +

    +
    + + + +
    + admin_img +

    + Click to Change +

    + +
    +
    +
    +
    + + +
    + {formErrors["name"] ? ( +
    * {formErrors["name"]}
    + ) : ( +
       
    + )} +
    +
    +
    + + +
    + {formErrors["position"] ? ( +
    * {formErrors["position"]}
    + ) : ( +
       
    + )} +
    +
    +
    + + +
    + {formErrors["company"] ? ( +
    * {formErrors["company"]}
    + ) : ( +
       
    + )} +
    +
    +
    + + +
    + {formErrors["text"] ? ( +
    * {formErrors["text"]}
    + ) : ( +
       
    + )} +
    +
    +
    + + +
    + {formErrors["rating"] ? ( +
    * {formErrors["rating"]}
    + ) : ( +
       
    + )} +
    +
    +
    +
    +
    + +
    + +
    +
    +
    + + {toast.toastStatus && ( + + )} +
    + ); +} diff --git a/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/add-testimonial.module.scss b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/add-testimonial.module.scss new file mode 100644 index 00000000..8347030d --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/add-testimonial.module.scss @@ -0,0 +1,267 @@ +.head { + text-align: center; +} + +.content { + overflow: hidden; + padding: 10px 20px; + font-size: 18px; + line-height: 1.2; + text-align: center; +} + +.upload-section { + position: relative; + background: linear-gradient( + 45deg, + rgba(255, 0, 90, 1) 0%, + rgba(10, 24, 61, 1) 90% + ); + cursor: pointer; + padding: 1rem; + height: auto; + margin-bottom: 1em; + display: flex; + align-items: center; + justify-content: center; + min-height: 10em; + border-radius: 20px; + flex-direction: column; +} + +.img-admin { + width: 50%; + margin: 1em; +} + +.crd { + min-width: 100px; + min-height: 12em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #016795; + box-shadow: rgba(141, 113, 113, 0.24) 0px 3px 8px; + cursor: pointer; + color: white; + border-radius: 20px; +} + +.crd:hover { + color: white; + background: #1b2431; +} + +.crd > .head1 { + display: flex; + justify-content: center; + align-items: center; + font-size: 1em; + margin: 0.5em; +} + +.crd > .head1 > h4 { + margin: 0 1em; + font-size: 24px; + font-weight: bold; +} + +@media (max-width: 600px) { + .manage-teams { + grid-template-columns: auto; + margin: 1em 0; + } + + .conts { + width: 90%; + } + + .content { + overflow: hidden; + padding: 1rem 1.2rem; + } +} + +@media screen and (min-width: 600px) and (max-width: 1050px) { + .content { + overflow: hidden; + padding: 1.5rem 3.5rem; + } +} +.add-testimonial-section { + background: #fff; + padding: 0 20px; +} + +.add-testimonial-parent { + display: flex; + padding: 30px 0; +} + +.add-testimonial-child { + flex: 1; +} + +.child1 { + flex-direction: column; + justify-content: space-around; + align-items: center; +} + +.add-testimonial-card { + width: 75%; + height: auto; + background-color: #e7e7e7; + margin-top: 8%; + margin-left: auto; + margin-right: auto; + padding-bottom: 20px; + border-radius: 10px; + box-shadow: 5px 5px 15px #888888, -5px -5px 15px #ffffff; +} + +.add-testimonial-header-text { + padding: 15px 0; + margin-bottom: 0px; + text-transform: capitalize; + color: var(--secondary-color); + text-align: center !important; +} +.add-testimonial-label-text { + padding: 0px 0px 15px 0px; + margin-bottom: 0px; + color: var(--secondary-color); + font-weight: 700; + font-size: medium; + color: gray; +} + +.inside-add-testimonial { + width: 85%; + margin: 0 auto; +} + +.add-testimonial-input { + position: relative; + margin-bottom: 20px; +} + +.add-testimonial-input input, +.add-testimonial-input textarea { + width: 100%; + height: 50px; + border: 1px solid #bbbaba; + border-radius: 10px; + padding: 0 25px; + margin-left: auto; + margin-right: auto; + color: #777777; + background-color: #f1f1f1; + box-shadow: inset 2px 2px 5px #888888, inset -2px -2px 5px #ffffff; +} + +.add-testimonial-input input::placeholder, +.add-testimonial-input textarea::placeholder { + opacity: 1; + color: #777777; +} + +.add-testimonial-input input::-moz-placeholder, +.add-testimonial-input textarea::-moz-placeholder { + opacity: 1; + color: #777777; +} + +.add-testimonial-input input::-webkit-input-placeholder, +.contact-input textarea::-webkit-input-placeholder { + opacity: 1; + color: #777777; +} + +.add-testimonial-input input:focus, +.add-testimonial-input textarea:focus { + border-color: #1863ff; + outline: none; + border: double 2px transparent; + border-radius: 10px; + background-image: linear-gradient(white, white), + linear-gradient(to right, rgba(255, 0, 90, 1), rgba(10, 24, 61, 1)); + background-origin: border-box; + background-clip: padding-box, border-box; + background-color: #ffffff; +} + +.add-testimonial-input textarea { + padding-top: 15px; + height: 180px; + resize: none; +} + +.add-testimonial-input i { + position: absolute; + right: 25px; + top: 15px; + font-size: 16px; + color: #777777; +} + +.dropdown { + --rmsc-main: #4285f4 !important; + --rmsc-selected: #474343 !important; + --rmsc-border: #838383 !important; + --rmsc-bg: #e7e7e7 !important; + --rmsc-p: 10px !important; /* Spacing */ + --rmsc-radius: 4px !important; /* Radius */ + --rmsc-h: 38px !important; /* Height */ +} + +.submit-btn { + justify-content: center; + width: 40%; + margin: 0 auto; +} + +.submit-btn-text { + display: flex; + justify-content: center; + align-items: center; +} + +@media screen and (max-width: 750px) { + .add-testimonial-parent { + display: block; + width: 100%; + } + + .add-testimonial-card { + width: 95%; + margin: 10% auto; + } + + .add-testimonial-input input, + .faqBtn { + height: 40px; + } + + .add-testimonial-btn { + font-size: 20px; + } + + .submit-btn { + width: 60% !important; + margin: 0 auto; + } + + .submit-btn-text { + justify-content: center; + align-items: center; + font-size: x-small; + color: black; + } +} + +.validation { + color: red; + margin-top: 0; +} diff --git a/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/index.js b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/index.js new file mode 100644 index 00000000..d8f3ad6a --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/AddTestimonial/index.js @@ -0,0 +1 @@ +export * from "./AddTestimonial"; diff --git a/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/ManageTestimonial.jsx b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/ManageTestimonial.jsx new file mode 100644 index 00000000..d2f9d28a --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/ManageTestimonial.jsx @@ -0,0 +1,95 @@ +import React, { useEffect, useState } from "react"; +import style from "./manage-testimonial.module.scss"; +import { SimpleToast } from "../../../../../components/util/Toast/Toast"; +import Loader from "../../../../../components/util/Loader"; +import { + deleteTestimonial, + getTestimonials, +} from "../../../../../service/Testimonial"; + +export function ManageTestimonial() { + const [testimonials, setTestimonials] = useState([]); + const [images, setImages] = useState([]); + const [toast, setToast] = useState({ + toastStatus: false, + toastType: "", + toastMessage: "", + }); + const [isLoaded, setIsLoaded] = useState(false); + + const getdata = async () => { + setIsLoaded(true); + await getTestimonials(setTestimonials, setToast); + setIsLoaded(false); + }; + + const handleDelete = async (id) => { + setIsLoaded(true); + await deleteTestimonial(id, setToast, toast); + await getdata(); + setIsLoaded(false); + }; + const handleCloseToast = (event, reason) => { + if (reason === "clickaway") { + return; + } + setToast({ ...toast, toastStatus: false }); + }; + + useEffect(() => { + getdata(); + }, []); + + return ( +
    +

    Manage Testimonials

    + {isLoaded ? ( +
    + +
    + ) : ( +
    + {testimonials?.map((testimonial, index) => ( +
    +
    + +
    +

    {testimonial.name}

    +
    +

    Position

    {testimonial.position} +
    +
    +

    Company

    {testimonial.company} +
    +
    +

    Testimonial

    {testimonial.text} +
    +
    +

    Rating

    {testimonial.rating} +
    + +
    + ))} +
    + )} + {toast.toastStatus && ( + + )} +
    + ); +} diff --git a/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/index.js b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/index.js new file mode 100644 index 00000000..6560ad95 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/index.js @@ -0,0 +1 @@ +export * from "./ManageTestimonial"; diff --git a/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/manage-testimonial.module.scss b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/manage-testimonial.module.scss new file mode 100644 index 00000000..ca13eb20 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/ManageTestimonial/manage-testimonial.module.scss @@ -0,0 +1,112 @@ +.head { + text-align: center; +} + +.manage-testimonials { + display: grid; + grid-template-columns: auto auto; + gap: 20px; + margin: 2em; + height: auto; +} + +.content { + overflow: hidden; + padding: 10px 20px; + font-size: 18px; + line-height: 1.2; + text-align: center; +} + +.content h3{ + font-weight: "bolder"; +} + +.crd { + min-width: 100px; + max-width: 440px; + min-height: 12em; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-color: #016795; + box-shadow: rgba(141, 113, 113, 0.24) 0px 3px 8px; + cursor: pointer; + color: white; + border-radius: 20px; +} + +.crd img { + margin: auto; + border-radius: 50%; + width: 200px; + height: 200px; + margin-top: 25px; +} +.crd:hover { + color: white; + background: #1b2431; +} + +.crd > .head1 { + display: flex; + justify-content: center; + align-items: center; + font-size: 1em; + margin: 0.5em; +} + +.crd > .head1 > h4 { + margin: 0 1em; + font-size: 24px; + font-weight: bold; +} + +@media (max-width: 983px) { + .manage-testimonials { + grid-template-columns: auto; + margin: 1em 0; + justify-content: center; + } + + .conts { + width: 90%; + } + .crd{ + max-width: 400px; + } + .content { + overflow-wrap: break-word; + padding: 1rem 1.2rem; + } +} + +@media screen and (min-width: 600px) and (max-width: 1050px) { + .content { + overflow: hidden; + padding: 1.5rem 3.5rem; + } +} + +.url{ + text-decoration: underline; + color: white; +} + +.url:hover{ + text-decoration: none; + color: white; + font-weight: 500; +} +.delete{ + width: 90px; + background-color: red; + color: white; + font-weight: bold; + margin-bottom: 20px; + margin-top: 15px; + padding: 10px; + border: none; + border-radius: 5px; +} \ No newline at end of file diff --git a/frontend/src/pages/Admin/Components/Testimonial/Testimonial.jsx b/frontend/src/pages/Admin/Components/Testimonial/Testimonial.jsx new file mode 100644 index 00000000..94f51d11 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/Testimonial.jsx @@ -0,0 +1,51 @@ +import React from "react"; +import style from "./testimonial.module.scss"; +import { AiFillEdit } from "react-icons/ai"; +import { AiOutlinePlus } from "react-icons/ai"; + +export function Testimonial(props) { + return ( +
    +

    Testimonial

    +
    +
    +
    +
    +
    props.setTab(21)} style={{ color: "white" }}> + CREATE TESTIMONIAL +
    + +
    +
    +

    To Create a new Testimonial

    +

    +

    props.setTab(21)} style={{ color: "red" }}> + CLICK HERE +
    +

    +
    +
    +
    +
    +
    +
    + MANAGE TESTIMONIAL + +
    +
    +
    props.setTab(22)} + className={style["main-btn"]} + > + Manage here +
    +
    +
    +
    +
    +
    + ); +} diff --git a/frontend/src/pages/Admin/Components/Testimonial/index.js b/frontend/src/pages/Admin/Components/Testimonial/index.js new file mode 100644 index 00000000..1a9c3046 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/index.js @@ -0,0 +1 @@ +export * from "./Testimonial"; diff --git a/frontend/src/pages/Admin/Components/Testimonial/testimonial.module.scss b/frontend/src/pages/Admin/Components/Testimonial/testimonial.module.scss new file mode 100644 index 00000000..07201e64 --- /dev/null +++ b/frontend/src/pages/Admin/Components/Testimonial/testimonial.module.scss @@ -0,0 +1,117 @@ +.broadcast { + display: flex; + flex-direction: column; + padding: 5px; + height: 100%; +} + +.cards { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; +} + +.card-item { + background-color: #016795; + font-size: 1.5rem; + border-radius: 1em; + box-shadow: 1em 1em 1em #363535; + width: 35%; + margin: 1em; + transition: background 0.3s ease-out; +} + +.clickable-card { + display: flex; + flex-direction: column; + justify-content: center; + padding: 1.5em; + color: var(--bs-light); + height: 380px; +} + +.card-title { + font-size: 1.8rem; + margin-bottom: 1.5rem; + line-height: 1.9rem; + font-weight: bold; +} + +.card-title:hover { + cursor: pointer; +} + +.card-content { + font-weight: normal; + text-align: center; + font-size: 1.2rem; + width: 100%; +} + +.card-item:hover { + background-color: #1b2431; +} + +.editt { + margin-left: 8px; + color: red; + margin-bottom: 20px; +} + +.add { + margin-left: 5px; + margin-bottom: 6px; +} + +.main-btn { + font-family: "Futura LT Book"; + display: inline-block; + font-weight: 500; + display: flex; + justify-content: center; + align-items: center; + white-space: nowrap; + vertical-align: middle; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + padding: 0 40px; + font-size: 16px; + line-height: 48px; + border-radius: 50px; + color: black; + cursor: pointer; + z-index: 5; + -webkit-transition: all 0.4s ease-out 0s; + -moz-transition: all 0.4s ease-out 0s; + -ms-transition: all 0.4s ease-out 0s; + -o-transition: all 0.4s ease-out 0s; + transition: all 0.4s ease-out 0s; + background-color: #fff; + border: 0; +} + +.main-btn:hover { + background-color: #fff; + color: #1863ff; + -webkit-box-shadow: 0px 5px 29px 0px rgba(0, 0, 0, 0.25); + -moz-box-shadow: 0px 5px 29px 0px rgba(0, 0, 0, 0.25); + box-shadow: 0px 5px 29px 0px rgba(0, 0, 0, 0.25); +} + +@media screen and (max-width: 768px) { + .cards { + flex-direction: column; + } + + .card-item { + width: 90%; + margin: 1em auto; + } + + .main-btn { + text-align: center; + } +} diff --git a/frontend/src/service/Testimonial.jsx b/frontend/src/service/Testimonial.jsx index 261e9246..cc9db870 100644 --- a/frontend/src/service/Testimonial.jsx +++ b/frontend/src/service/Testimonial.jsx @@ -1,7 +1,8 @@ +import { DELETE_FAIL, DELETE_SUCCESS, POST_FAIL, POST_SUCCESS } from "../common/constants"; import { END_POINT } from "../config/api"; import { showToast } from "./toastService"; -export async function getTestimonials(setTestimonials, setToast) { +async function getTestimonials(setTestimonials, setToast) { try { const response = await fetch(`${END_POINT}/testimonials/getTestimonials`, { method: "GET", @@ -23,3 +24,66 @@ export async function getTestimonials(setTestimonials, setToast) { } }; +const deleteTestimonial = async (id, setToast,toast) => { + try { + const response = await fetch(`${END_POINT}/testimonials/${id}`, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + }); + if (!response.ok) { + throw new Error("Failed to delete testimonial"); + } + setToast({ + ...toast, + toastMessage: DELETE_SUCCESS, + toastStatus: true, + toastType: "success", + }); + } catch (err) { + console.error("Network Error:", err); + setToast({ + ...toast, + toastMessage: DELETE_FAIL, + toastStatus: true, + toastType: "error", + }); + } +} + +const addTestimonial = async (testimonial, setToast,toast) => { + try { + const response = await fetch(`${END_POINT}/testimonials/`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${localStorage.getItem("token")}`, + }, + body: JSON.stringify(testimonial), + }); + const res = await response.json(); + if (!response.ok) { + console.log(res); + throw new Error("Failed to add testimonial"); + } + setToast({ + ...toast, + toastMessage: POST_SUCCESS, + toastStatus: true, + toastType: "success", + }); + } catch (err) { + console.error("Network Error:", err); + setToast({ + ...toast, + toastMessage: POST_FAIL, + toastStatus: true, + toastType: "error", + }); + } +} + + +export { getTestimonials, addTestimonial, deleteTestimonial} \ No newline at end of file