diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e16d433b..5877a5fc 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -4,8 +4,14 @@ module.exports = { es2021: true, node: true, }, - extends: "airbnb", - plugins: ["prettier"], + extends: [ + "airbnb", + "airbnb/hooks", + "eslint:recommended", + // "plugin:prettier/recommended", + "plugin:react/recommended", + ], + plugins: ["react", "prettier"], overrides: [ { env: { @@ -24,8 +30,30 @@ module.exports = { rules: { semi: 0, "comma-dangle": 0, - "prettier/prettier": "error", "react/jsx-filename-extension": [1, { extensions: [".js", ".jsx"] }], quotes: ["error", "double"], + "linebreak-style": ["error", "unix"], + "no-console": "error", + "react/react-in-jsx-scope": 0, + "import/order": [ + "error", + { + groups: [ + "builtin", + "external", + "internal", + "parent", + "sibling", + "index", + ], + alphabetize: { order: "asc", caseInsensitive: true }, + }, + ], + "prettier/prettier": [ + "error", + { + endOfLine: "auto", + }, + ], }, }; diff --git a/.vs/ProjectSettings.json b/.vs/ProjectSettings.json new file mode 100644 index 00000000..f8b48885 --- /dev/null +++ b/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/.vs/SYT-Web-Redesign/FileContentIndex/7875359b-7b73-4b72-9718-0b7b3c711662.vsidx b/.vs/SYT-Web-Redesign/FileContentIndex/7875359b-7b73-4b72-9718-0b7b3c711662.vsidx new file mode 100644 index 00000000..cad8ff7f Binary files /dev/null and b/.vs/SYT-Web-Redesign/FileContentIndex/7875359b-7b73-4b72-9718-0b7b3c711662.vsidx differ diff --git a/.vs/SYT-Web-Redesign/v17/.wsuo b/.vs/SYT-Web-Redesign/v17/.wsuo new file mode 100644 index 00000000..2d707b7c Binary files /dev/null and b/.vs/SYT-Web-Redesign/v17/.wsuo differ diff --git a/.vs/VSWorkspaceState.json b/.vs/VSWorkspaceState.json new file mode 100644 index 00000000..6b611411 --- /dev/null +++ b/.vs/VSWorkspaceState.json @@ -0,0 +1,6 @@ +{ + "ExpandedNodes": [ + "" + ], + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/.vs/slnx.sqlite b/.vs/slnx.sqlite new file mode 100644 index 00000000..5a258830 Binary files /dev/null and b/.vs/slnx.sqlite differ diff --git a/package.json b/package.json index 9439c340..02ed4264 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "start": "react-scripts start", "test": "react-scripts test", "eject": "react-scripts eject", - "pretty": "prettier --write ." + "pretty": "prettier --write .", + "knip": "knip" }, "dependencies": { "@emailjs/browser": "^3.11.0", @@ -32,7 +33,6 @@ "react-fast-marquee": "^1.6.2", "react-hook-form": "^7.48.2", "react-photo-album": "^2.3.0", - "react-query": "^3.39.3", "react-router-dom": "^6.11.2", "react-simple-wysiwyg": "^2.2.5", "tailwind-scrollbar-hide": "^1.1.7", @@ -51,13 +51,12 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.3.4", "husky": "^8.0.0", - "lint-staged": "^13.2.2", + "knip": "^4.3.1", "postcss": "^8.4.23", "prettier": "^2.8.8", "tailwindcss": "^3.3.2", - "vite": "^4.3.2" + "vite": "^4.5.1" }, "lint-staged": { "**/*.{js,jsx}": [ diff --git a/src/ADMIN/index.js b/src/ADMIN/index.js deleted file mode 100644 index d3724d52..00000000 --- a/src/ADMIN/index.js +++ /dev/null @@ -1,17 +0,0 @@ -import AllBlogsPage from "./pages/blogs/AllBlogsPage"; -import AddChapterPage from "./pages/chapters/AddChapterPage"; -import AllChaptersPage from "./pages/chapters/AllChaptersPage"; -import AddEventPage from "./pages/events/AddEventPage"; -import AllEventsPage from "./pages/events/AllEventsPage"; -import UpdateEventPage from "./pages/events/UpdateEventPage"; -import AdminLayout from "./components/AdminLayout"; - -export { - AddChapterPage, - AddEventPage, - AllBlogsPage, - AllChaptersPage, - AllEventsPage, - UpdateEventPage, - AdminLayout, -}; diff --git a/src/ADMIN/pages/chapters/AddChapterPage.jsx b/src/ADMIN/pages/chapters/AddChapterPage.jsx deleted file mode 100644 index 8c614818..00000000 --- a/src/ADMIN/pages/chapters/AddChapterPage.jsx +++ /dev/null @@ -1,164 +0,0 @@ -import * as React from "react"; -import { useNavigate } from "react-router-dom"; -import StepIndicator from "../../components/StepIndicator"; -import AddChapterForm from "../../components/AddChapterForm"; -import usePostAddChapter from "../../../hooks/Queries/chapter/usePostAddChapter"; - -function AddChapterPage() { - const { - setChapterData: postChapter, - error: errorPostChapter, - clearError: clearErrorPostChapter, - status: statusPostChapter, - clearStatus: clearStatusPostChapter - } = usePostAddChapter(); - - const [ collectedChapter, setCollectedChapter ] = React.useState(null); - - const initialSteps = [ - { section: "01", title: "GENERAL INFO", status: "active" }, - { section: "02", title: "SOCIAL MEDIA LINKS", status: "inactive" }, - { section: "03", title: "ORGANIZERS", status: "inactive" }, - ]; - - const [steps, setSteps] = React.useState(initialSteps); - - const [currentStep, setCurrentStep] = React.useState(0); - const navigate = useNavigate(); - - const handleNextStep = () => { - if (currentStep < steps.length - 1) { - const updatedSteps = [...steps]; - updatedSteps[currentStep].status = "completed"; - updatedSteps[currentStep + 1].status = "active"; - setSteps(updatedSteps); - setCurrentStep(currentStep + 1); - } - }; - - const handlePreviousStep = () => { - if (currentStep > 0) { - const updatedSteps = [...steps]; - updatedSteps[currentStep].status = "inactive"; - updatedSteps[currentStep - 1].status = "inactive"; - setSteps(updatedSteps); - setCurrentStep(currentStep - 1); - } - }; - - const formComplete = (completeChapterData) => { - statusPostChapter === 'error' && clearStatusPostChapter(); - errorPostChapter && clearErrorPostChapter(); - postChapter({...completeChapterData}); - } - - React.useEffect(() => { - if (statusPostChapter === 'success') { - setSteps(initialSteps); - setCurrentStep(0); - setCollectedChapter(null); - } - }, [statusPostChapter]); - - return ( -
- - -
-

- Chapters -

-

- New Chapter -

- {/* Success Display */} - {statusPostChapter==="success" && -
- Chapter Added Successfully! - { - clearStatusPostChapter(); - postChapter(null); - }} > - Close - -
- } - {/* Errors Display */} - {statusPostChapter==="error" && errorPostChapter?.axios && -
- Cannot add the chapter: - {errorPostChapter.axios} - { - clearErrorPostChapter(); - clearStatusPostChapter(); - }} > - Close - -
- } - {statusPostChapter==="error" && errorPostChapter?.chapter && -
- Cannot add the chapter: -
    - {Object.keys(errorPostChapter.chapter).map(key => ( -
  • - {key}: {errorPostChapter.chapter[key].toString()} -
  • - ))} -
- { - clearErrorPostChapter(); - clearStatusPostChapter(); - }} > - Close - -
- } -

- Add Chapter Details -

-
-
- - -
-
- ); -} - -export default AddChapterPage; diff --git a/src/APP/components/Footer2.jsx b/src/APP/components/Footer2.jsx index 68f75255..400ab29c 100644 --- a/src/APP/components/Footer2.jsx +++ b/src/APP/components/Footer2.jsx @@ -95,7 +95,10 @@ function Footer2() { Community
  • - Products + Products +
  • +
  • + Shop
  • @@ -128,7 +131,6 @@ function Footer2() { Gallery - @@ -160,7 +162,6 @@ function Footer2() { special offers

    -
    Subscribe @@ -178,7 +178,6 @@ function Footer2() { -
    diff --git a/src/ADMIN/components/AddChapterForm.jsx b/src/APP/components/admin/AddChapterForm.jsx similarity index 100% rename from src/ADMIN/components/AddChapterForm.jsx rename to src/APP/components/admin/AddChapterForm.jsx diff --git a/src/ADMIN/components/AdminHeader.jsx b/src/APP/components/admin/AdminHeader.jsx similarity index 93% rename from src/ADMIN/components/AdminHeader.jsx rename to src/APP/components/admin/AdminHeader.jsx index 8b8de007..3b99ccae 100644 --- a/src/ADMIN/components/AdminHeader.jsx +++ b/src/APP/components/admin/AdminHeader.jsx @@ -1,8 +1,8 @@ /* eslint-disable react/react-in-jsx-scope */ import { Link } from "react-router-dom"; -import logo from "../../assets/images/sytLogo.png"; -import bell from "../../assets/images/icons/bell-icon.svg"; -import profile from "../../assets/images/adminPage/profile-pic.png"; +import logo from "../../../assets/images/sytLogo.png"; +import bell from "../../../assets/images/icons/bell-icon.svg"; +import profile from "../../../assets/images/adminPage/profile-pic.png"; function AdminHeader() { return ( diff --git a/src/ADMIN/components/AdminLayout.jsx b/src/APP/components/admin/AdminLayout.jsx similarity index 100% rename from src/ADMIN/components/AdminLayout.jsx rename to src/APP/components/admin/AdminLayout.jsx diff --git a/src/ADMIN/components/BottomNavBar.jsx b/src/APP/components/admin/BottomNavBar.jsx similarity index 100% rename from src/ADMIN/components/BottomNavBar.jsx rename to src/APP/components/admin/BottomNavBar.jsx diff --git a/src/ADMIN/components/DropdownInput.jsx b/src/APP/components/admin/DropdownInput.jsx similarity index 100% rename from src/ADMIN/components/DropdownInput.jsx rename to src/APP/components/admin/DropdownInput.jsx diff --git a/src/ADMIN/components/GeneralInfo.jsx b/src/APP/components/admin/GeneralInfo.jsx similarity index 100% rename from src/ADMIN/components/GeneralInfo.jsx rename to src/APP/components/admin/GeneralInfo.jsx diff --git a/src/ADMIN/components/Organizers.jsx b/src/APP/components/admin/Organizers.jsx similarity index 100% rename from src/ADMIN/components/Organizers.jsx rename to src/APP/components/admin/Organizers.jsx diff --git a/src/ADMIN/components/SocialMediaLinks.jsx b/src/APP/components/admin/SocialMediaLinks.jsx similarity index 100% rename from src/ADMIN/components/SocialMediaLinks.jsx rename to src/APP/components/admin/SocialMediaLinks.jsx diff --git a/src/ADMIN/components/StepIndicator.jsx b/src/APP/components/admin/StepIndicator.jsx similarity index 100% rename from src/ADMIN/components/StepIndicator.jsx rename to src/APP/components/admin/StepIndicator.jsx diff --git a/src/ADMIN/components/events/Calendar.jsx b/src/APP/components/admin/events/Calendar.jsx similarity index 100% rename from src/ADMIN/components/events/Calendar.jsx rename to src/APP/components/admin/events/Calendar.jsx diff --git a/src/ADMIN/components/events/EventsTable.jsx b/src/APP/components/admin/events/EventsTable.jsx similarity index 99% rename from src/ADMIN/components/events/EventsTable.jsx rename to src/APP/components/admin/events/EventsTable.jsx index 8482c49f..e14a4899 100644 --- a/src/ADMIN/components/events/EventsTable.jsx +++ b/src/APP/components/admin/events/EventsTable.jsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import SearchIcon from "../../../assets/images/icons/search-icon.svg"; +import SearchIcon from "../../../../assets/images/icons/search-icon.svg"; const initialData = [ { diff --git a/src/APP/components/auth/NotificationModal.jsx b/src/APP/components/auth/NotificationModal.jsx new file mode 100644 index 00000000..65ec351d --- /dev/null +++ b/src/APP/components/auth/NotificationModal.jsx @@ -0,0 +1,83 @@ +import { Dialog, Transition } from "@headlessui/react"; +import PropTypes from "prop-types"; +import { Fragment } from "react"; +import { Link } from "react-router-dom"; + +function NotificationModal({ isOpen, setIsOpen, message }) { + const closeModal = () => { + setIsOpen(false); + }; + + return ( + + + +
    + + +
    +
    + + + + Before you continue... 😃 + + +
    +

    + Please{" "} + + Log In{" "} + + or{" "} + + Create an account{" "} + {" "} +
    + {message}. +

    +
    +
    +
    +
    +
    +
    +
    + ); +} + +export default NotificationModal; + +NotificationModal.propTypes = { + isOpen: PropTypes.bool.isRequired, + setIsOpen: PropTypes.func.isRequired, + message: PropTypes.string.isRequired, +}; diff --git a/src/APP/components/index.js b/src/APP/components/index.js index 8447ce1d..34e49c51 100644 --- a/src/APP/components/index.js +++ b/src/APP/components/index.js @@ -4,7 +4,8 @@ import { default as Footer2 } from "./Footer2"; import { default as Header } from "./Header"; import { default as Header2 } from "./Header2"; import { default as Caroussel } from "./Caroussel"; -import { default as Counter } from "./Counter"; +import { default as Counter } from "./shop/Counter"; +import { default as CartDrawer } from "./shop/CartDrawer"; import { default as PodcastCard } from "./PodcastCard"; import { default as Loader } from "./Loader"; @@ -18,4 +19,5 @@ export { Counter, PodcastCard, Loader, + CartDrawer, }; diff --git a/src/APP/components/shop/CartDrawer.jsx b/src/APP/components/shop/CartDrawer.jsx new file mode 100644 index 00000000..562b9ffd --- /dev/null +++ b/src/APP/components/shop/CartDrawer.jsx @@ -0,0 +1,234 @@ +import { Dialog, Transition } from "@headlessui/react"; +import { Fragment, useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import CloseIcon from "../../../assets/images/icons/close-icon.svg"; +import DeleteIcon from "../../../assets/images/icons/delete-icon.svg"; +import SampleImg from "../../../assets/images/shop-page/main-sample.png"; +import Sample3 from "../../../assets/images/shop-page/sample3.png"; +import { useDeleteSwag } from "../../../hooks/Mutations/shop/useCartSwagg"; +import useProductsInCart from "../../../hooks/Queries/shop/useCartProducts"; +import useAuth from "../../../hooks/useAuth"; +import Counter from "./Counter"; + +function CartDrawer({ open, setOpen }) { + const { auth } = useAuth(); + const navigate = useNavigate(); + const [count, setCount] = useState(1); + + const { data: cartProducts, isSuccess } = useProductsInCart(); + + const { mutate: deleteSwag } = useDeleteSwag(); + + const handleDeleteSwag = (cartItemId) => { + deleteSwag(cartItemId); + }; + + const handleCheckout = () => { + if (auth?.access) { + navigate("/shop/checkout"); + } + }; + + return ( + + + +
    + + +
    +
    +
    + + +
    +
    + + Your cart{" "} + + ({isSuccess ? cartProducts.cart_items?.length : 0}) + + +
    + +
    +
    + +
    + {/* Items in Cart */} +
    +
    +
      + {isSuccess && + cartProducts.cart_items?.map( + ({ + id, + product: { + id: productId, + image, + name, + price, + size, + }, + quantity, + }) => ( +
    • +
      + {name} +
      + +
      +
      +

      +

      + {" "} + + {name} + +

      +

      + +
      +
      +

      + Ksh {price} +

      + + {/* Count thing reason out */} + +
      +
      +
    • + ) + )} +
    +
    +
    + + {/* Recommendation items */} +
    +

    + {" < "} + You might love {" > "} +

    +
    +
    + Mentorlst Hoodie +
    +
    +

    + Mentorlst Hoodie +

    +
    +

    + Ksh 1500 +

    + +
    +
    +
    +
    +
    + + {/* Sub Total */} +
    +
    +

    Sub Total

    +

    + Ksh {isSuccess ? cartProducts.total_price : "00"} +

    +
    +
    + + +
    +

    + By selecting ‘Check Out’ you are agreeing to our{" "} + + Terms & Conditions + +

    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ); +} + +export default CartDrawer; diff --git a/src/APP/components/Counter.jsx b/src/APP/components/shop/Counter.jsx similarity index 65% rename from src/APP/components/Counter.jsx rename to src/APP/components/shop/Counter.jsx index eafe0eba..e4252a23 100644 --- a/src/APP/components/Counter.jsx +++ b/src/APP/components/shop/Counter.jsx @@ -1,9 +1,13 @@ -function Counter({ className }) { +import PropTypes from "prop-types"; + +function Counter({ className, setCount, count }) { return (
    @@ -11,11 +15,13 @@ function Counter({ className }) { className="outline-none focus:outline-none font-medium md:text-basecursor-default flex items-center justify-center border-y border-y-[#323433]" name="custom-input-number" > - 1 + {count}

    @@ -24,3 +30,13 @@ function Counter({ className }) { } export default Counter; + +Counter.defaultProps = { + className: "", +}; + +Counter.propTypes = { + className: PropTypes.string, + setCount: PropTypes.func.isRequired, + count: PropTypes.number.isRequired, +}; diff --git a/src/APP/index.js b/src/APP/index.js index a971e167..7288a6ae 100644 --- a/src/APP/index.js +++ b/src/APP/index.js @@ -1,43 +1,55 @@ -import LandingPage from "./pages/landingPage/LandingPage"; - -import Homepage from "./pages/shop/Homepage"; - -import SingleItemPage from "./pages/shop/pages/SingleItemPage"; -import Layout from "./pages/Layout"; -import Products from "./pages/products2/Products"; -import Resources from "./pages/resources2/Resources"; - -import Checkout from "./pages/shop/pages/OrderSummary"; - import AboutUs from "./pages/aboutUs/AboutUs"; +import AllBlogsPage from "./pages/admin/blogs/AllBlogsPage"; +import AddChapterPage from "./pages/admin/chapters/AddChapterPage"; +import AllChaptersPage from "./pages/admin/chapters/AllChaptersPage"; +import AddEventPage from "./pages/admin/events/AddEventPage"; +import AllEventsPage from "./pages/admin/events/AllEventsPage"; +import UpdateEventPage from "./pages/admin/events/UpdateEventPage"; +import ForgotPassword from "./pages/auth/ForgotPassword"; +import LogIn from "./pages/auth/LogIn"; +import ResetPassword from "./pages/auth/ResetPassword"; +import SignUp from "./pages/auth/SignUp"; +import Blog from "./pages/blog/Blog"; +import Blogs from "./pages/blogs/Blogs"; +import IndividualChapter from "./pages/chapter/pages/IndividualChapter"; import CommunityPage from "./pages/community/CommunityPage"; -import DonatePage from "./pages/donate/DonatePage"; -import Categories from "./pages/shop/pages/Categories"; import SingleEvent from "./pages/community/sections/eventsSection/SingleEvents/SingleEvent"; -import EventsPage from "./pages/events/pages/EventsPage"; -import EventsSection from "./pages/events/sections/eventsSection/EventsSection"; -import Blogs from "./pages/blogs/Blogs"; -import Blog from "./pages/blog/Blog"; +import DonatePage from "./pages/donate/DonatePage"; import SingleProductDonation from "./pages/donate/pages/SingleProductDonatePage"; -import IndividualChapter from "./pages/chapter/pages/IndividualChapter"; import Error400 from "./pages/errorPages/Error400"; import Error403 from "./pages/errorPages/Error403"; import Error404 from "./pages/errorPages/Error404"; import Error500 from "./pages/errorPages/Error500"; - -import ProductDisplay from "./pages/shop/pages/ProductDisplay"; +import ErrorBoundary from "./pages/errorPages/ErrorBoundary"; +import EventsPage from "./pages/events/pages/EventsPage"; +import EventsSection from "./pages/events/sections/eventsSection/EventsSection"; import GalleryPage from "./pages/gallery/GalleryPage"; +import LandingPage from "./pages/landingPage/LandingPage"; +import Layout from "./pages/Layout"; +import Products from "./pages/products2/Products"; +import Resources from "./pages/resources/Resources"; +import Homepage from "./pages/shop/Homepage"; +import Checkout from "./pages/shop/OrderSummaryPage"; +import ProductDisplay from "./pages/shop/ProductDisplayPage"; +import CategoriesProducts from "./pages/shop/sections/CategoriesProducts"; +import SingleItemPage from "./pages/shop/SingleItemPage"; export { + AddChapterPage, + AddEventPage, + AllBlogsPage, + AllChaptersPage, + AllEventsPage, + UpdateEventPage, LandingPage, Homepage, Checkout, + CategoriesProducts, SingleItemPage, Layout, Products, Resources, AboutUs, - Categories, CommunityPage, DonatePage, SingleEvent, @@ -51,6 +63,11 @@ export { Error403, Error404, Error500, + ErrorBoundary, ProductDisplay, + ForgotPassword, + LogIn, + ResetPassword, + SignUp, GalleryPage, }; diff --git a/src/APP/pages/Layout.jsx b/src/APP/pages/Layout.jsx index 93fedcf7..00392256 100644 --- a/src/APP/pages/Layout.jsx +++ b/src/APP/pages/Layout.jsx @@ -1,19 +1,18 @@ +import { useEffect } from "react"; import { Outlet, useLocation } from "react-router-dom"; - import { Header2, Footer2 } from "../components"; -import { useEffect } from "react"; -const ScrollToTopOnLinkClick = () => { - const{ pathname } = useLocation(); +function ScrollToTopOnLinkClick() { + const { pathname } = useLocation(); useEffect(() => { window.scrollTo(0, 0); }, [pathname]); return null; -}; +} -const Layout = () => { +function Layout() { return (
    @@ -22,6 +21,6 @@ const Layout = () => {
    ); -}; +} export default Layout; diff --git a/src/APP/pages/aboutUs/sections/LeadershipSection.jsx b/src/APP/pages/aboutUs/sections/LeadershipSection.jsx index 686548c0..296a6d13 100644 --- a/src/APP/pages/aboutUs/sections/LeadershipSection.jsx +++ b/src/APP/pages/aboutUs/sections/LeadershipSection.jsx @@ -187,8 +187,9 @@ function LeadershipSection() { /> { + if (currentStep < steps.length - 1) { + const updatedSteps = [...steps]; + updatedSteps[currentStep].status = "completed"; + updatedSteps[currentStep + 1].status = "active"; + setSteps(updatedSteps); + setCurrentStep(currentStep + 1); + } + }; + + const handlePreviousStep = () => { + if (currentStep > 0) { + const updatedSteps = [...steps]; + updatedSteps[currentStep].status = "inactive"; + updatedSteps[currentStep - 1].status = "inactive"; + setSteps(updatedSteps); + setCurrentStep(currentStep - 1); + } + }; + + const formComplete = (completeChapterData) => { + statusPostChapter === "error" && clearStatusPostChapter(); + errorPostChapter && clearErrorPostChapter(); + postChapter({ ...completeChapterData }); + }; + + useEffect(() => { + if (statusPostChapter === "success") { + setSteps(initialSteps); + setCurrentStep(0); + setCollectedChapter(null); + } + }, [statusPostChapter]); + + console.log(statusPostChapter); + + return ( +
    + + +
    +

    + Chapters +

    +

    + New Chapter +

    + {/* Success Display */} + {statusPostChapter === "success" && ( +
    + Chapter Added Successfully! + { + clearStatusPostChapter(); + postChapter(null); + }} + > + + Close + + + +
    + )} + {/* Errors Display */} + {statusPostChapter === "error" && errorPostChapter?.axios && ( +
    + Cannot add the chapter: + {errorPostChapter.axios} + { + clearErrorPostChapter(); + clearStatusPostChapter(); + }} + > + + Close + + + +
    + )} + {statusPostChapter === "error" && errorPostChapter?.chapter && ( +
    + Cannot add the chapter: +
      + {Object.keys(errorPostChapter.chapter).map((key) => ( +
    • + {key}: {" "} + {errorPostChapter.chapter[key].toString()} +
    • + ))} +
    + { + clearErrorPostChapter(); + clearStatusPostChapter(); + }} + > + + Close + + + +
    + )} +

    + Add Chapter Details +

    +
    +
    + + +
    +
    + ); +} + +export default AddChapterPage; diff --git a/src/ADMIN/pages/chapters/AllChaptersPage.jsx b/src/APP/pages/admin/chapters/AllChaptersPage.jsx similarity index 100% rename from src/ADMIN/pages/chapters/AllChaptersPage.jsx rename to src/APP/pages/admin/chapters/AllChaptersPage.jsx diff --git a/src/ADMIN/pages/events/AddEventPage.jsx b/src/APP/pages/admin/events/AddEventPage.jsx similarity index 98% rename from src/ADMIN/pages/events/AddEventPage.jsx rename to src/APP/pages/admin/events/AddEventPage.jsx index 7206f5ce..d10fedca 100644 --- a/src/ADMIN/pages/events/AddEventPage.jsx +++ b/src/APP/pages/admin/events/AddEventPage.jsx @@ -1,4 +1,6 @@ +import { yupResolver } from "@hookform/resolvers/yup"; import React, { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; import { BtnBold, BtnBulletList, @@ -15,13 +17,11 @@ import { EditorProvider, Toolbar, } from "react-simple-wysiwyg"; -import { Controller, useForm } from "react-hook-form"; import * as yup from "yup"; -import { yupResolver } from "@hookform/resolvers/yup"; -import { useEventsCategories } from "../../../hooks/Queries/eventsSection/useEventCategories"; -import useChaptersData from "../../../hooks/Queries/community/useChaptersData"; -import usePostEvents from "../../../hooks/Queries/eventsSection/usePostEvents"; +import useChaptersData from "../../../../hooks/Queries/community/useChaptersData"; +import { useEventsCategories } from "../../../../hooks/Queries/eventsSection/useEventCategories"; +import usePostEvents from "../../../../hooks/Queries/eventsSection/usePostEvents"; function AddEventPage() { const [selectedEventCategory, setSelectedEventCategory] = useState("1"); @@ -316,7 +316,7 @@ function AddEventPage() { {errors.category_name.message} )} -
    +
    ; + // return ; + navigate(-1); } return ( @@ -80,7 +82,7 @@ function LogIn() { Forgot password? @@ -90,12 +92,12 @@ function LogIn() { type="submit" className="bg-primary hover:bg-[#00664E] text-white text-xl rounded border-0 py-3 px-5 sm:px-8 w-full focus:outline-none" > - Login + Log In

    New here? {" "} diff --git a/src/AUTH/pages/ResetPassword.jsx b/src/APP/pages/auth/ResetPassword.jsx similarity index 96% rename from src/AUTH/pages/ResetPassword.jsx rename to src/APP/pages/auth/ResetPassword.jsx index c2598446..cb0d10e2 100644 --- a/src/AUTH/pages/ResetPassword.jsx +++ b/src/APP/pages/auth/ResetPassword.jsx @@ -1,5 +1,5 @@ import React from "react"; -import ResetPasswordImg from "../../assets/images/auth/reset-password.svg"; +import ResetPasswordImg from "../../../assets/images/auth/reset-password.svg"; function ResetPassword() { return ( diff --git a/src/APP/pages/auth/SignUp.jsx b/src/APP/pages/auth/SignUp.jsx new file mode 100644 index 00000000..f17e98f6 --- /dev/null +++ b/src/APP/pages/auth/SignUp.jsx @@ -0,0 +1,160 @@ +import { useState } from "react"; +import { Navigate } from "react-router-dom"; +import publicAxios from "../../../api/publicAxios"; +import SignUpImg from "../../../assets/images/auth/signup.svg"; +import useAuth from "../../../hooks/useAuth"; + +function SignUp() { + const { auth, setAuth } = useAuth(); + const [username, setUsername] = useState(""); + const [firstname, setFirstname] = useState(""); + const [lastname, setLastname] = useState(""); + const [email, setEmail] = useState(""); + const [phoneNumber, setPhoneNumber] = useState(""); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [isError, setError] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + const getToken = async () => { + try { + const response = await publicAxios.post( + "/token/", + { + username, + password, + }, + { headers: { "Content-Type": "application/json" } } + ); + + setAuth(response.data); + } catch (error) { + setError(error.message); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + setIsLoading(true); + + try { + const response = await publicAxios.post( + `${process.env.REACT_APP_API_BASE_URL}/users/`, + { + username, + first_name: firstname, + last_name: lastname, + email, + phone_number: phoneNumber, + password, + confirm_password: confirmPassword, + }, + { headers: { "Content-Type": "application/json" } } + ); + + if (response.data) getToken(); + } catch (error) { + setError(error.message); + } + }; + + if (auth?.access) { + return ; + } + + return ( +

    + ); +} + +export default SignUp; diff --git a/src/APP/pages/blog/Blog.jsx b/src/APP/pages/blog/Blog.jsx index 4e949891..33400151 100644 --- a/src/APP/pages/blog/Blog.jsx +++ b/src/APP/pages/blog/Blog.jsx @@ -1,30 +1,25 @@ - import React, { useEffect } from "react"; import { useParams, useNavigate } from "react-router-dom"; - - +import useBlogData from "../../../hooks/Queries/blog/useBlogData"; +import { Loader } from "../../components"; import BlogWrapper from "./sections/BlogWrapper"; import RelatedBlogs from "./sections/RelatedBlogs"; -import { Loader } from "../../components"; -import useBlogData from "../../../hooks/Queries/blog/useBlogData"; function Blog() { - const { title_slug } = useParams(); + const { titleSlug } = useParams(); const navigate = useNavigate(); const { data: blogData, refetch: refetchBlogData, - isLoading, isError, isSuccess, - } = useBlogData(title_slug); - + } = useBlogData(titleSlug); useEffect(() => { refetchBlogData(); - }, [title_slug]); + }, [titleSlug]); return (
    @@ -39,22 +34,20 @@ function Blog() {
    )} {isSuccess && ( - <> -
    - {blogData.title} - - - - -
    - +
    + {blogData.title} + + + + +
    )}
    ); diff --git a/src/APP/pages/blog/sections/BlogWrapper.jsx b/src/APP/pages/blog/sections/BlogWrapper.jsx index d5eaa39f..8904d387 100644 --- a/src/APP/pages/blog/sections/BlogWrapper.jsx +++ b/src/APP/pages/blog/sections/BlogWrapper.jsx @@ -1,62 +1,61 @@ -import React from "react"; -import { formatDistanceToNow } from "date-fns"; - -// import Comments from "./Comments"; -import BlogStats from "../../blogs/sections/BlogStats"; -import logo from "../../../../assets/images/sytLogo.png"; - -import "./blogWrapper.css"; - -const BlogWrapper = ({ blog }) => { - - const timeAgo = formatDistanceToNow(new Date(blog?.created_at), { - addSuffix: true, - }); - - return ( -
    -
    -
    -

    - {blog.title} -

    - -
    -
    - icon - -
    -

    - {blog.author} -

    - - {timeAgo} -
    -
    - - -
    -
    - -
    -
    -
    -
    -
    - - {/* */} -
    - -
    {/* Advert?? */}
    -
    - ); -}; - -export default BlogWrapper; +import { formatDistanceToNow } from "date-fns"; +import React from "react"; + +// import Comments from "./Comments"; +import logo from "../../../../assets/images/sytLogo.png"; +import BlogStats from "../../blogs/sections/BlogStats"; + +import "./blogWrapper.css"; + +function BlogWrapper({ blog }) { + const timeAgo = formatDistanceToNow(new Date(blog?.created_at), { + addSuffix: true, + }); + + return ( +
    +
    +
    +

    + {blog.title} +

    + +
    +
    + icon + +
    +

    + {blog.author} +

    + + {timeAgo} +
    +
    + + +
    +
    + +
    +
    +
    +
    +
    + + {/* */} +
    + +
    {/* Advert?? */}
    +
    + ); +} + +export default BlogWrapper; diff --git a/src/APP/pages/blog/sections/RelatedBlogs.jsx b/src/APP/pages/blog/sections/RelatedBlogs.jsx index 04819087..cc60f48b 100644 --- a/src/APP/pages/blog/sections/RelatedBlogs.jsx +++ b/src/APP/pages/blog/sections/RelatedBlogs.jsx @@ -29,10 +29,9 @@ function RelatedBlogs({ blogId, categoryId }) { {isError &&

    Error loading blogs!

    } {isLoading &&

    Loading blogs...

    } - {isSuccess && filteredRelatedBlogs.length > 0 && ( <> -

    +

    {filteredRelatedBlogs.length > 1 ? "Related Articles" : "Related Article"} @@ -53,7 +52,6 @@ function RelatedBlogs({ blogId, categoryId }) { )}

    - )} ); diff --git a/src/APP/pages/blogs/sections/BlogCard.jsx b/src/APP/pages/blogs/sections/BlogCard.jsx index 6b9f1590..1717d63a 100644 --- a/src/APP/pages/blogs/sections/BlogCard.jsx +++ b/src/APP/pages/blogs/sections/BlogCard.jsx @@ -1,74 +1,75 @@ -import React from "react"; -import { useNavigate, Link } from "react-router-dom"; -import { formatDistanceToNow } from "date-fns"; - -import { arrowRight } from "../../../../assets/images/blogs-page"; -import logo from "../../../../assets/images/sytLogo.png"; -import BlogStats from "./BlogStats"; - -function BlogCard({ blog }) { - const navigate = useNavigate(); - const timeAgo = - blog?.created_at && - formatDistanceToNow(new Date(blog?.created_at), { - addSuffix: true, - }); - - return ( - - blog - -
    -
    -

    {blog.title}

    - - -
    - -

    - {blog.description} -

    - -
    -
    - icon - -
    -

    - {blog.author} -

    - - {timeAgo} -
    -
    - - -
    -
    - - ); -} - -export default BlogCard; +/* eslint-disable react/prop-types */ +import React from "react"; +import { useNavigate, Link } from "react-router-dom"; +import { formatDistanceToNow } from "date-fns"; + +import { arrowRight } from "../../../../assets/images/blogs-page"; +import logo from "../../../../assets/images/sytLogo.png"; +import BlogStats from "./BlogStats"; + +function BlogCard({ blog }) { + const navigate = useNavigate(); + + const timeAgo = + blog?.created_at && + formatDistanceToNow(new Date(blog?.created_at), { + addSuffix: true, + }); + + return ( + + blog + +
    +
    +

    {blog.title}

    + + +
    + +

    + {blog.description} +

    + +
    +
    + icon + +
    +

    + {blog.author} +

    + {timeAgo} +
    +
    + + +
    +
    + + ); +} + +export default BlogCard; diff --git a/src/APP/pages/blogs/sections/BlogsWrapper.jsx b/src/APP/pages/blogs/sections/BlogsWrapper.jsx index 41473eee..44c75acb 100644 --- a/src/APP/pages/blogs/sections/BlogsWrapper.jsx +++ b/src/APP/pages/blogs/sections/BlogsWrapper.jsx @@ -1,13 +1,9 @@ -/* eslint-disable operator-linebreak */ -/* eslint-disable react/jsx-indent */ import React, { useState, useEffect, useContext } from "react"; -// import { useNavigate } from "react-router-dom"; + import BlogCard from "./BlogCard"; import BlogPagination from "./BlogPagination"; - import { Loader } from "../../../components"; - import { SearchBlogContext } from "../../../../context/searchBlog"; import { useBlogsData, @@ -29,7 +25,6 @@ function SearchResults({ searchText }) { function BlogsWrapper() { const { searchText, searchBlog } = useContext(SearchBlogContext); - const [selectedCat, setSelectedCat] = useState(""); const [page, setPage] = useState(1); diff --git a/src/APP/pages/errorPages/Error400.jsx b/src/APP/pages/errorPages/Error400.jsx index 753b451a..636558e3 100644 --- a/src/APP/pages/errorPages/Error400.jsx +++ b/src/APP/pages/errorPages/Error400.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { Link } from "react-router-dom"; import { error400 } from "../../../assets/images/errorPages"; @@ -16,18 +15,18 @@ function Error400() {

    There was a problem with your request. If you continue to see this error, please{" "} - + contact us. - + .

    - Go Home - +
    ); diff --git a/src/APP/pages/errorPages/Error403.jsx b/src/APP/pages/errorPages/Error403.jsx index e40f22d5..800daec6 100644 --- a/src/APP/pages/errorPages/Error403.jsx +++ b/src/APP/pages/errorPages/Error403.jsx @@ -1,5 +1,4 @@ import React from "react"; -import { Link } from "react-router-dom"; import { error403 } from "../../../assets/images/errorPages"; @@ -16,18 +15,18 @@ function Error403() {

    You don’t have permission to view this resource. If you believe there has been a mistake, please{" "} - + contact us. - + .

    - Go Home - +
    ); diff --git a/src/APP/pages/errorPages/Error500.jsx b/src/APP/pages/errorPages/Error500.jsx index 2c879d4e..bfeb1a75 100644 --- a/src/APP/pages/errorPages/Error500.jsx +++ b/src/APP/pages/errorPages/Error500.jsx @@ -1,6 +1,4 @@ import React from "react"; -import { Link } from "react-router-dom"; - import { bgError500, error500svg } from "../../../assets/images/errorPages"; function Error500() { @@ -30,17 +28,18 @@ function Error500() {

    We’re experiencing an internal server problem. Please try again after a few minutes or{" "} - + contact us. - +

    - Go Home - + +
    @@ -60,17 +59,17 @@ function Error500() {

    We’re experiencing an internal server problem. Please try again after a few minutes or{" "} - + contact us. - +

    - Go Home - + ); diff --git a/src/APP/pages/errorPages/ErrorBoundary.jsx b/src/APP/pages/errorPages/ErrorBoundary.jsx new file mode 100644 index 00000000..34e1d5bf --- /dev/null +++ b/src/APP/pages/errorPages/ErrorBoundary.jsx @@ -0,0 +1,33 @@ +import React, { useState, useEffect } from "react"; + +import Error500 from "./Error500"; + +function ErrorBoundary({ children }) { + const [hasError, setHasError] = useState(false); + + useEffect(() => { + const errorHandler = (error) => { + console.error( + "Error: ", + error.message, + "ErrorFileName: ", + error.filename + ); + setHasError(true); + }; + + window.addEventListener("error", errorHandler); + + return () => { + window.removeEventListener("error", errorHandler); + }; + }, []); + + if (hasError) { + return ; + } + + return <>{children}; +} + +export default ErrorBoundary; diff --git a/src/APP/pages/events/sections/eventsSection/Events.jsx b/src/APP/pages/events/sections/eventsSection/Events.jsx index 2fa4cbec..b2d657f1 100644 --- a/src/APP/pages/events/sections/eventsSection/Events.jsx +++ b/src/APP/pages/events/sections/eventsSection/Events.jsx @@ -8,7 +8,7 @@ function Events({ events, isVertical }) { const verticalContainer = "my-6 grid grid-cols-1 gap-x-3 gap-y-10 sm:grid-cols-2 lg:grid-cols-5 xl:gap-x-8"; const horizontalContainer = - "flex overflow-x-auto px-0 md:px-4 justify-between gap-2 sm:gap-4 w-full"; + "flex overflow-x-auto px-0 md:px-4 gap-2 sm:gap-4 w-full"; // Event Card classes const verticalWrapper = diff --git a/src/APP/pages/events/sections/eventsSection/EventsUpdateSection.jsx b/src/APP/pages/events/sections/eventsSection/EventsUpdateSection.jsx index aa63d953..4d161cd3 100644 --- a/src/APP/pages/events/sections/eventsSection/EventsUpdateSection.jsx +++ b/src/APP/pages/events/sections/eventsSection/EventsUpdateSection.jsx @@ -1,7 +1,5 @@ import React from "react"; // , { useState } - - import { Link } from "react-router-dom"; function EventsUpdateSection({ diff --git a/src/APP/pages/products2/data.js b/src/APP/pages/products2/data.js index 82bfc0fc..9ffd73b4 100644 --- a/src/APP/pages/products2/data.js +++ b/src/APP/pages/products2/data.js @@ -29,6 +29,7 @@ import { js, laravel, mentorlst, + colabs, notion, python, react, @@ -46,7 +47,7 @@ export const products = [ { name: "Colabs", desc: "An open-source directory with beginner-friendly open-source projects across all technologies and tech stacks. Browse through hundreds of projects or list your open-source projects and onboard contributors.", - img: mentorlst, + img: colabs, link: "https://spaceyatech.github.io/CoLabs/", }, // { diff --git a/src/APP/pages/shop/Homepage.jsx b/src/APP/pages/shop/Homepage.jsx index 20f7bdd2..88b9e749 100644 --- a/src/APP/pages/shop/Homepage.jsx +++ b/src/APP/pages/shop/Homepage.jsx @@ -1,12 +1,14 @@ import Banner from "./sections/Banner"; import CategoriesSection from "./sections/CategoriesSection"; import PopularItemsSection from "./sections/PopularItemsSection"; +// import CategoriesProducts from "./sections/CategoriesProducts"; function Homepage() { return (
    + {/* */}
    ); diff --git a/src/APP/pages/shop/pages/OrderSummary.jsx b/src/APP/pages/shop/OrderSummaryPage.jsx similarity index 57% rename from src/APP/pages/shop/pages/OrderSummary.jsx rename to src/APP/pages/shop/OrderSummaryPage.jsx index 2b107295..dca0e567 100644 --- a/src/APP/pages/shop/pages/OrderSummary.jsx +++ b/src/APP/pages/shop/OrderSummaryPage.jsx @@ -1,32 +1,36 @@ /* eslint-disable react/react-in-jsx-scope */ -import { useState } from "react"; -import ItemHeader from "../sections/ItemHeader"; -import Sample1 from "../../../../assets/images/shop-page/sample1.png"; -import Sample2 from "../../../../assets/images/shop-page/sample2.png"; -import Counter from "../../../components/Counter"; -import { useOrderSummary } from "../../../../hooks/Queries/shop/useOrdersList"; - -const products = [ - { - id: 1, - name: "SYT Hoodie", - href: "#", - color: "Salmon", - price: "90.00", - quantity: 1, - imageSrc: Sample1, - }, - { - id: 2, - name: "SYT Bookmark", - href: "#", - color: "Blue", - price: "32.00", - quantity: 1, - imageSrc: Sample2, - }, - // More products... -]; +import { Dialog, Transition } from "@headlessui/react"; +import { useState, Fragment } from "react"; +import { Link } from "react-router-dom"; +import Sample1 from "../../../assets/images/shop-page/sample1.png"; +import Sample2 from "../../../assets/images/shop-page/sample2.png"; +import useMakeOrder from "../../../hooks/Mutations/shop/useMakeOrder"; +import useProductsInCart from "../../../hooks/Queries/shop/useCartProducts"; +import { useOrderSummary } from "../../../hooks/Queries/shop/useOrdersList"; +import Counter from "../../components/shop/Counter"; +import ItemHeader from "./sections/ItemHeader"; + +// const products = [ +// { +// id: 1, +// name: "SYT Hoodie", +// href: "#", +// color: "Salmon", +// price: "90.00", +// quantity: 1, +// imageSrc: Sample1, +// }, +// { +// id: 2, +// name: "SYT Bookmark", +// href: "#", +// color: "Blue", +// price: "32.00", +// quantity: 1, +// imageSrc: Sample2, +// }, +// // More products... +// ]; const steps = [ { description: "Provide your MPESA mobile number", key: 1 }, @@ -43,9 +47,40 @@ const steps = [ ]; function Checkout() { + const { data: products, isSuccess } = useProductsInCart(); + const { + mutate: makeOrder, + isLoading, + isSuccess: successfulOrder, + } = useMakeOrder(); + const [open, setOpen] = useState(false); + const [count, setCount] = useState(1); + const [email, setEmail] = useState(""); + const [name, setName] = useState(""); + const [county, setCounty] = useState(""); + const [address, setAddress] = useState(""); + const [city, setCity] = useState(""); + const [postalCode, setPostalCode] = useState(""); + const [street, setStreet] = useState(""); + const [phoneNumber, setPhoneNumber] = useState(""); + + const [isOpen, setIsOpen] = useState(false); + // const { data: orderSummary, status } = useOrderSummary(); - const { data: orderSummary, status } = useOrderSummary(); + const handleSubmit = (e) => { + const payload = { + address, + phonenumber: phoneNumber, + }; + makeOrder(payload); + }; + + const closeModal = () => { + setIsOpen(false); + }; + + console.log("products ", products); return ( <> @@ -62,26 +97,25 @@ function Checkout() {
    setEmail(e.target.value)} /> - {/*

    - Please fill out this field. -

    */}
    setName(e.target.value)} />
    @@ -89,11 +123,12 @@ function Checkout() {
    setCounty(e.target.value)} />
    @@ -101,11 +136,12 @@ function Checkout() {
    setAddress(e.target.value)} />
    @@ -113,20 +149,22 @@ function Checkout() {
    setCity(e.target.value)} />
    setPostalCode(e.target.value)} />
    @@ -134,11 +172,12 @@ function Checkout() {
    setStreet(e.target.value)} />
    @@ -146,11 +185,12 @@ function Checkout() {
    setPhoneNumber(e.target.value)} />
    @@ -165,32 +205,44 @@ function Checkout() {
      - {products.map( - ({ id, imageSrc, name, href, price, color, quantity }) => ( -
    • -
      - {name} -
      - -
      -

      - {" "} - {name} -

      - -

      Ksh {price}

      - -
      -
    • - ) - )} + {isSuccess && + products.cart_items?.map( + ({ + id, + product: { id: productId, image, name, price }, + quantity, + }) => ( +
    • +
      + {name} +
      + +
      +

      + {" "} + {name} +

      + +

      + Ksh + {price} +

      + +
      +
    • + ) + )}
    @@ -199,13 +251,23 @@ function Checkout() {

    Shipping

    -

    Ksh 3600

    -

    Ksh 350

    +

    + Ksh + {isSuccess && products.total_price} +

    +

    + Ksh + {isSuccess && products.total_price * 0.1} +

    Total

    -

    Ksh 3950

    +

    + Ksh{" "} + {isSuccess && + products.total_price * 0.1 + products.total_price} +

    @@ -222,7 +284,7 @@ function Checkout() {
      {steps.map(({ description, key }) => (
    1. - {description} + {key}.{description}
    2. ))}
    @@ -242,11 +304,61 @@ function Checkout() { + {successfulOrder && ( + + + +
    + + +
    +
    + + + + Payment Successful! + + +
    +

    + Ksh {products.total_price * 0.1 + products.total_price}{" "} +

    +
    +
    +
    +
    +
    +
    +
    + )} ); } diff --git a/src/APP/pages/shop/pages/ProductDisplay.jsx b/src/APP/pages/shop/ProductDisplayPage.jsx similarity index 77% rename from src/APP/pages/shop/pages/ProductDisplay.jsx rename to src/APP/pages/shop/ProductDisplayPage.jsx index bc1a9761..4567e814 100644 --- a/src/APP/pages/shop/pages/ProductDisplay.jsx +++ b/src/APP/pages/shop/ProductDisplayPage.jsx @@ -1,6 +1,6 @@ import React from "react"; +import PopularItemsSection from "./sections/PopularItemsSection"; import SingleItemPage from "./SingleItemPage"; -import PopularItemsSection from "../sections/PopularItemsSection"; function ProductDisplay() { return ( diff --git a/src/APP/pages/shop/SingleItemPage.jsx b/src/APP/pages/shop/SingleItemPage.jsx new file mode 100644 index 00000000..da17787d --- /dev/null +++ b/src/APP/pages/shop/SingleItemPage.jsx @@ -0,0 +1,195 @@ +/* eslint-disable react/react-in-jsx-scope */ +import { Fragment, useState, useEffect } from "react"; +import { useNavigate, useParams } from "react-router-dom"; + +import Sample1 from "../../../assets/images/shop-page/sample1.png"; +import Sample2 from "../../../assets/images/shop-page/sample2.png"; +import SmallSample1 from "../../../assets/images/shop-page/small-sample-colored.png"; +import SmallSample2 from "../../../assets/images/shop-page/small-sample-greyscale.png"; +import { useAddSwagToCart } from "../../../hooks/Mutations/shop/useCartSwagg"; +import { useSingleOrder } from "../../../hooks/Queries/shop/useOrdersList"; +import { useSingleSwag } from "../../../hooks/Queries/shop/useSwagList"; +import useAuth from "../../../hooks/useAuth"; +import NotificationModal from "../../components/auth/NotificationModal"; +import CartDrawer from "../../components/shop/CartDrawer"; +import Counter from "../../components/shop/Counter"; +import ItemHeader from "./sections/ItemHeader"; + +const products = [ + { + id: 1, + name: "SYT Hoodie", + href: "#", + color: "Salmon", + price: "90.00", + quantity: 1, + imageSrc: Sample1, + }, + { + id: 2, + name: "SYT Bookmark", + href: "#", + color: "Blue", + price: "32.00", + quantity: 1, + imageSrc: Sample2, + }, + // More products... +]; + +const VariationData = [SmallSample1, SmallSample2, SmallSample1, SmallSample2]; + +export default function SingleItemPage() { + const { auth } = useAuth(); + const params = useParams(); + // const navigate = useNavigate(); + + const [count, setCount] = useState(1); + const [open, setOpen] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); + const [message, setMessage] = useState(""); + const [selectedSize, setSelectedSize] = useState(null); + const [Payload, setPayload] = useState({}); + + const { data: singleOrder } = useSingleOrder(params.id); + const { data: singleSwag, isSuccess, refetch } = useSingleSwag(params.id); + const { mutate: addItemsToCart, isLoading } = useAddSwagToCart(); + + const sizes = ["XS", "S", "M", "L", "XL", "XXL"]; + + console.log("singleSwag", params); + useEffect(() => { + if (isSuccess) { + setPayload({ + swagg_id: singleSwag.id, + product: { + name: singleSwag.name, + description: singleSwag.description, + price: singleSwag.price, + size: selectedSize, + }, + quantity: count, + }); + } + refetch(); + }, [params.id]); + + const handleAddToCart = () => { + if (auth?.access) { + addItemsToCart(Payload); + setOpen(true); + } else { + setMessage("to add items to cart"); + setIsModalOpen(true); + } + }; + + const handleBuyNow = () => { + if (auth?.access) { + addItemsToCart(Payload); + setOpen(true); + } else { + setMessage("to make an order"); + setIsModalOpen(true); + } + }; + + return ( + <> + setOpen((prev) => !prev)} /> + {isSuccess ? ( +
    +
    + {singleSwag.name} +
    + {VariationData.map((pic) => ( +
    + recommendation +
    + ))} +
    +
    + +
    +

    + {singleSwag.name} +

    +

    + Product description +

    +

    {singleSwag.description}

    +

    + Ksh {singleSwag.price} +

    +

    Choose color

    + +
    + {VariationData.map((pic) => ( +
    + +
    + ))} +
    + +

    Choose size

    +
    + {sizes.map((size) => ( + + ))} +
    + + + + + +
    +
    + ) : ( +

    Error Fetching Item

    + )} + + {/* Drawer */} + + + {/* Notification Modal */} + + + ); +} diff --git a/src/APP/pages/shop/pages/Categories.jsx b/src/APP/pages/shop/pages/Categories.jsx deleted file mode 100644 index 152afea4..00000000 --- a/src/APP/pages/shop/pages/Categories.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import CategoriesProducts from "../sections/CategoriesProducts"; -import CategoriesSection from "../sections/CategoriesSection"; - -function Homepage() { - return ( -
    - -
    - ); - } - - export default Homepage; diff --git a/src/APP/pages/shop/pages/SingleItemPage.jsx b/src/APP/pages/shop/pages/SingleItemPage.jsx deleted file mode 100644 index af7ede7f..00000000 --- a/src/APP/pages/shop/pages/SingleItemPage.jsx +++ /dev/null @@ -1,323 +0,0 @@ -/* eslint-disable react/react-in-jsx-scope */ -import { Fragment, useState } from "react"; -import { Dialog, Transition } from "@headlessui/react"; -import { useNavigate } from "react-router-dom"; -import SampleImg from "../../../../assets/images/shop-page/main-sample.png"; -import SmallSample1 from "../../../../assets/images/shop-page/small-sample-colored.png"; -import SmallSample2 from "../../../../assets/images/shop-page/small-sample-greyscale.png"; -import Sample1 from "../../../../assets/images/shop-page/sample1.png"; -import Sample2 from "../../../../assets/images/shop-page/sample2.png"; -import Sample3 from "../../../../assets/images/shop-page/sample3.png"; -import CloseIcon from "../../../../assets/images/icons/close-icon.svg"; -import DeleteIcon from "../../../../assets/images/icons/delete-icon.svg"; -import Counter from "../../../components/Counter"; -import ItemHeader from "../sections/ItemHeader"; -import useProductsInCart from "../../../../hooks/Queries/shop/useCartProducts"; -import { useSingleOrder } from "../../../../hooks/Queries/shop/useOrdersList"; - -const products = [ - { - id: 1, - name: "SYT Hoodie", - href: "#", - color: "Salmon", - price: "90.00", - quantity: 1, - imageSrc: Sample1, - }, - { - id: 2, - name: "SYT Bookmark", - href: "#", - color: "Blue", - price: "32.00", - quantity: 1, - imageSrc: Sample2, - }, - // More products... -]; - -const VariationData = [SmallSample1, SmallSample2, SmallSample1, SmallSample2]; - -export default function SingleItemPage() { - const [open, setOpen] = useState(true); - const [ProductsInCart, setProductsInCart] = useState(null); - - const navigate = useNavigate(); - const { data: cartProducts, status } = useProductsInCart(); - const { data: singleOrder } = useSingleOrder(); - - // console.log("cart Products", cartProducts); - return ( - <> - setOpen((prev) => !prev)} /> -
    -
    - Sample -
    - {VariationData.map((pic, i) => ( -
    - recommendation -
    - ))} -
    -
    - -
    -

    - SpaceYaTech Hoodie -

    -

    - Product description -

    -

    - Our Iconic T-Shirt is crafted with meticulous attention to detail, - using superior materials that ensure durability and long-lasting - performance. It's a wardrobe investment that will continue to - impress you with its impeccable construction and ability to - withstand the test of time. -

    -

    - Ksh 1500 -

    -

    Choose color

    - -
    - {VariationData.map((pic, i) => ( -
    - -
    - ))} -
    - -

    Choose size

    -
    - - - - -
    - - - - - -
    -
    - - {/* Drawer */} - - - -
    - - -
    -
    -
    - - -
    -
    - - Your cart (4) - -
    - -
    -
    - -
    - {/* Items in Cart */} -
    -
    -
      - {status === "success" && - products.map( - ({ - id, - imageSrc, - name, - href, - price, - color, - quantity, - }) => ( -
    • -
      - {name} -
      - -
      -
      -

      -

      - {" "} - {name} -

      -

      - -
      -
      -

      - Ksh - {price} -

      - - -
      -
      -
    • - ) - )} -
    -
    -
    - - {/* Recommendation items */} -
    -

    - {" < "} - You might love {" > "} -

    -
    -
    - Mentorlst Hoodie -
    -
    -

    - Mentorlst Hoodie -

    -
    -

    - Ksh 1500 -

    - -
    -
    -
    -
    -
    - - {/* Sub Total */} -
    -
    -

    Sub Total

    -

    Ksh 3600

    -
    -
    - - -
    -

    - By selecting ‘Check Out’ you are agreeing to our{" "} - Terms & Conditions -

    -
    -
    -
    -
    -
    -
    -
    -
    -
    - - ); -} diff --git a/src/APP/pages/shop/sections/CategoriesProducts.jsx b/src/APP/pages/shop/sections/CategoriesProducts.jsx index b6672c27..2a959735 100644 --- a/src/APP/pages/shop/sections/CategoriesProducts.jsx +++ b/src/APP/pages/shop/sections/CategoriesProducts.jsx @@ -1,11 +1,10 @@ -import { useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import { useSwagList } from "../../../../hooks/Queries/shop/useSwagList"; import ItemHeader from "./ItemHeader"; -import useSwagList from "../../../../hooks/Queries/shop/useSwagList"; function CategoriesProducts() { const params = useParams(); - const navigate = useNavigate(); const [products, setProducts] = useState([ { id: 1, @@ -74,8 +73,15 @@ function CategoriesProducts() { ]); const [open, setOpen] = useState(true); - const { data: swagList, status } = useSwagList(); + const { data: swagList, isSuccess } = useSwagList(); + useEffect(() => { + setProducts( + swagList?.filter( + (item) => item.category.toLowerCase() === params?.category.toLowerCase() + ) || [] + ); + }, [swagList, params]); return ( <> @@ -87,16 +93,16 @@ function CategoriesProducts() {
    - {products.map((product) => { - return ( -
    ( + navigate(`/shop/item/${product.id}`)} + to={`/shop/item/${product.id}`} >
    Front of men's Basic Tee in black. @@ -104,22 +110,15 @@ function CategoriesProducts() {

    - - - {product.name} - + {product.name}

    {product.price}

    -
    - ); - })} + + ))}
    diff --git a/src/APP/pages/shop/sections/CategoriesSection.jsx b/src/APP/pages/shop/sections/CategoriesSection.jsx index d61235a3..678e2b10 100644 --- a/src/APP/pages/shop/sections/CategoriesSection.jsx +++ b/src/APP/pages/shop/sections/CategoriesSection.jsx @@ -1,7 +1,8 @@ -import { Link } from "react-router-dom"; -import { useState } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useState } from "react"; +import { Link } from "react-router-dom"; +import { useSwagList } from "../../../../hooks/Queries/shop/useSwagList"; import ItemHeader from "./ItemHeader"; function CategoriesSection() { @@ -51,6 +52,24 @@ function CategoriesSection() { ]); const [open, setOpen] = useState(true); + const { data, isLoading, isError, isSuccess } = useSwagList(); + + // setCategories(data.results.map((item) => item.category)); + // eslint-disable-next-line prefer-const + let categoriesHash = {}; + isSuccess && + data.forEach((item) => { + if (!categoriesHash[item.category]) { + categoriesHash[item.category] = item.image; + } + }); + + // eslint-disable-next-line prefer-const + let categoryList = Object.keys(categoriesHash).map((item) => ({ + name: item.toLowerCase(), + imgURL: categoriesHash[item], + })); + return ( <> setOpen((prev) => !prev)} /> @@ -61,31 +80,32 @@ function CategoriesSection() {
    - {categories.map((category) => { - return ( -
    - - {category.name} + {categoryList.map((category) => ( +
    + + {category.name} + +
    +

    + {category.name} +

    + + View More + -
    -

    - {category.name} -

    - - View More{" "} - - -
    - ); - })} +
    + ))}
    diff --git a/src/APP/pages/shop/sections/ItemHeader.jsx b/src/APP/pages/shop/sections/ItemHeader.jsx index b2e3da40..ad7b110a 100644 --- a/src/APP/pages/shop/sections/ItemHeader.jsx +++ b/src/APP/pages/shop/sections/ItemHeader.jsx @@ -1,46 +1,152 @@ -import { useState } from "react"; -import SearchIcon from "../../../../assets/images/icons/search-icon.svg"; +import { faCheck, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Combobox, Transition } from "@headlessui/react"; +import { Fragment, useState } from "react"; +import { useLocation, useParams, Link } from "react-router-dom"; import CartIcon from "../../../../assets/images/icons/cart-icon.svg"; +import { useSwagList } from "../../../../hooks/Queries/shop/useSwagList"; function ItemHeader({ show }) { + const { pathname } = useLocation(); + const { id } = useParams(); + + const pathnames = pathname + .split("/") + .filter((path) => path && path !== "category" && path !== id); + + const { data: swag, isSuccess } = useSwagList(); + + const [selected, setSelected] = useState(isSuccess && swag[0]); + const [query, setQuery] = useState(""); + + const filteredSwag = + query === "" + ? swag + : swag.filter((item) => + item.name + .toLowerCase() + .replace(/\s+/g, "") + .includes(query.toLowerCase().replace(/\s+/g, "")) + ); + return (
    {/* Breadcrumb */} {/* Search box */} -
    - -
    diff --git a/src/APP/pages/shop/sections/PopularItemsSection.jsx b/src/APP/pages/shop/sections/PopularItemsSection.jsx index 45e11410..e133bd8a 100644 --- a/src/APP/pages/shop/sections/PopularItemsSection.jsx +++ b/src/APP/pages/shop/sections/PopularItemsSection.jsx @@ -1,74 +1,10 @@ -import { useState } from "react"; import { useNavigate } from "react-router-dom"; +import { useSwagList } from "../../../../hooks/Queries/shop/useSwagList"; function PopularItemsSection() { const navigate = useNavigate(); - const [products, setProducts] = useState([ - { - id: 1, - name: "Bookmarks", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1588618575327-87bfc763efd2?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=865&q=80", - price: "Ksh 500", - }, - { - id: 2, - name: "SpaceYaTech Hoodie", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=436&q=80", - price: "Ksh 500", - }, - { - id: 3, - name: "Mentorist T-shirt", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=436&q=80", - price: "Ksh 500", - }, - { - id: 4, - name: "SpaceYaTech Mug", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1936&q=80", - price: "Ksh 500", - }, - { - id: 5, - name: "Bookmarks", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1588618575327-87bfc763efd2?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=865&q=80", - price: "Ksh 500", - }, - { - id: 6, - name: "SpaceYaTech Hoodie", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=436&q=80", - price: "Ksh 500", - }, - { - id: 7, - name: "Mentorist T-shirt", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=436&q=80", - price: "Ksh 500", - }, - { - id: 8, - name: "SpaceYaTech Mug", - href: "#", - imageSrc: - "https://images.unsplash.com/photo-1591047139829-d91aecb6caea?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1936&q=80", - price: "Ksh 500", - }, - ]); + + const { data: products, isLoading, isError, isSuccess } = useSwagList(); return (
    @@ -76,39 +12,41 @@ function PopularItemsSection() { Popular items from SpaceYaTech
    - {products.map((product) => { - return ( -
    navigate(`/shop/item/${product.id}`)} - > -
    - Front of men's Basic Tee in black. -
    -
    -
    -

    - - - {product.name} - -

    -

    - {product.price} -

    + {products + ?.slice(0, 8) + .map( + ({ available, category, description, id, name, image, price }) => { + return ( +
    navigate(`/shop/item/${id}`)} + > +
    + {name} +
    +
    +
    +

    + + {name} +

    +

    + {price} +

    +
    +
    -
    -
    - ); - })} + ); + } + )}
    ); diff --git a/src/AUTH/index.js b/src/AUTH/index.js deleted file mode 100644 index 994818d8..00000000 --- a/src/AUTH/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import ForgotPassword from "./pages/ForgotPassword"; -import LogIn from "./pages/LogIn"; -import ResetPassword from "./pages/ResetPassword"; -import SignUp from "./pages/SignUp"; -import Validate from "./pages/Validate"; - -export { ForgotPassword, LogIn, ResetPassword, SignUp, Validate }; diff --git a/src/AUTH/pages/SignUp.jsx b/src/AUTH/pages/SignUp.jsx deleted file mode 100644 index cb2a4fad..00000000 --- a/src/AUTH/pages/SignUp.jsx +++ /dev/null @@ -1,75 +0,0 @@ -import React from "react"; -import SignUpImg from "../../assets/images/auth/signup.svg"; - -function SignUp() { - return ( -
    -
    -

    - Welcome to SpaceyaTech -
    - Shop! -

    -

    Let's get you started

    - Sign Up Page -
    - -
    -

    Create Your Account

    - -
    - - - - - - - - Continue - -

    - Already have an account? - - {" "} - Login - -

    -
    -
    -
    - ); -} - -export default SignUp; diff --git a/src/AUTH/pages/Validate.jsx b/src/AUTH/pages/Validate.jsx deleted file mode 100644 index 69788520..00000000 --- a/src/AUTH/pages/Validate.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -function Validate() { - return
    🥸 Hang on while we redirect you...
    ; -} - -export default Validate; diff --git a/src/api/privateAxios.js b/src/api/privateAxios.js new file mode 100644 index 00000000..6755890a --- /dev/null +++ b/src/api/privateAxios.js @@ -0,0 +1,98 @@ +import axios from "axios"; +import publicAxios from "./publicAxios"; + +const BASE_URL = process.env.REACT_APP_API_BASE_URL; + +const privateAxios = axios.create({ + baseURL: BASE_URL, +}); + +let isRefreshing = false; +let refreshPromise = null; + +let failedQueue = []; + +const processQueue = (error, token = null) => { + failedQueue.forEach((prom) => { + if (error) { + prom.reject(error); + } else { + prom.resolve(token); + } + }); + + failedQueue = []; +}; + +privateAxios.interceptors.response.use( + (response) => response, + async (error) => { + const errorResponse = error.response?.data; + + if (error.response && errorResponse.statusCode === 401) { + const originalRequest = error.config; + + if (!originalRequest._retry) { + originalRequest._retry = true; + + if (!isRefreshing) { + isRefreshing = true; + + refreshPromise = publicAxios + .post("/token/refresh", { + refreshString: JSON.parse(localStorage.getItem("auth") || "{}") + .refresh, + }) + .then((response) => { + if (response.data.access) { + const authObject = { + ...JSON.parse(localStorage.getItem("auth") || "{}"), + access: response.data.access, + refresh: response.data.refresh, + }; + + localStorage.setItem("auth", JSON.stringify(authObject)); + originalRequest.headers.Authorization = `Bearer ${authObject.access}`; + processQueue(null, response.data.access); + + return response; + } + localStorage.removeItem("auth"); + window.location.href = "/login"; + return Promise.reject(error); + }) + .catch((err) => { + processQueue(err, null); + + if (err.response.status === 401 || err.response.status === 404) { + localStorage.removeItem("auth"); + + window.location.href = "/login"; + + return Promise.reject(err); + } + + throw err; + }) + .finally(() => { + isRefreshing = false; + }); + } + + return new Promise((resolve, reject) => { + failedQueue.push({ resolve, reject }); + }) + .then((token) => { + originalRequest.headers.Authorization = `Bearer ${token}`; + + return axios(originalRequest); + }) + .catch((err) => Promise.reject(err)); + } + } + + return Promise.reject(error); + } +); + +export default privateAxios; diff --git a/src/api/publicAxios.js b/src/api/publicAxios.js new file mode 100644 index 00000000..2fbe0289 --- /dev/null +++ b/src/api/publicAxios.js @@ -0,0 +1,10 @@ +import axios from "axios"; + +const BASE_URL = process.env.REACT_APP_API_BASE_URL; + +const publicAxios = axios.create({ + baseURL: BASE_URL, + headers: { "Content-Type": "application/json" }, +}); + +export default publicAxios; diff --git a/src/assets/images/products/colabs.png b/src/assets/images/products/colabs.png new file mode 100644 index 00000000..9b96888b Binary files /dev/null and b/src/assets/images/products/colabs.png differ diff --git a/src/assets/images/products/index.js b/src/assets/images/products/index.js index c7a265e4..a3f48bfe 100644 --- a/src/assets/images/products/index.js +++ b/src/assets/images/products/index.js @@ -1,4 +1,5 @@ import mentorlst from "./mentorlst.png"; +import colabs from "./colabs.png"; import innovation from "./innovation.mp4"; import figma from "./stacks/figma.png"; @@ -24,6 +25,7 @@ import kubernetes from "./stacks/kubernetes.png"; export { mentorlst, innovation, + colabs, figma, notion, whimsical, diff --git a/src/utils/AuthContext.jsx b/src/context/AuthContext.jsx similarity index 95% rename from src/utils/AuthContext.jsx rename to src/context/AuthContext.jsx index 4d869ded..8d19c693 100644 --- a/src/utils/AuthContext.jsx +++ b/src/context/AuthContext.jsx @@ -27,8 +27,6 @@ export function AuthContextProvider({ children }) { setAuth(null); }; - // console.log("AUTHrityad", auth) - return ( {children} diff --git a/src/hooks/Mutations/shop/useCartSwagg.jsx b/src/hooks/Mutations/shop/useCartSwagg.jsx new file mode 100644 index 00000000..1da212fd --- /dev/null +++ b/src/hooks/Mutations/shop/useCartSwagg.jsx @@ -0,0 +1,67 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import privateAxios from "../../../api/privateAxios"; +import useAuth from "../../useAuth"; + +// !!! choose colorSwagg +const useAddSwagToCart = () => { + const { auth, logout } = useAuth(); + const queryClient = useQueryClient(); + + return useMutation( + async (cartItems) => { + const response = await privateAxios.post("/cart/swaggs/", cartItems, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${auth?.access}`, + }, + }); + return response.data; + }, + { + onSuccess: (data) => { + console.log("Added to cart: ", data); + queryClient.invalidateQueries(["productsInCart"]); + }, + onError: (error) => { + // eslint-disable-next-line no-console + console.error("Unable to add availability"); + if (error.response.status === 401) { + logout(); + } + }, + } + ); +}; + +const useDeleteSwag = () => { + const { auth, logout } = useAuth(); + const queryClient = useQueryClient(); + + return useMutation( + async (id) => { + const response = await privateAxios.delete(`/cart/swaggs/${id}/`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${auth?.access}`, + }, + }); + console.log("response ", response.data); + return response.data; + }, + { + onSuccess: (data) => { + console.log("Successfully deleted ", data); + queryClient.invalidateQueries(["productsInCart"]); + }, + onError: (error) => { + // eslint-disable-next-line no-console + console.error("Unable to delete swag"); + if (error.response.status === 401) { + logout(); + } + }, + } + ); +}; + +export { useAddSwagToCart, useDeleteSwag }; diff --git a/src/hooks/Mutations/shop/useMakeOrder.jsx b/src/hooks/Mutations/shop/useMakeOrder.jsx new file mode 100644 index 00000000..c3ecf2ea --- /dev/null +++ b/src/hooks/Mutations/shop/useMakeOrder.jsx @@ -0,0 +1,36 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import privateAxios from "../../../api/privateAxios"; +import useAuth from "../../useAuth"; + +// POST: https://apis.spaceyatech.com/api/orders/ +const useMakeOrder = () => { + const { auth, logout } = useAuth(); + const queryClient = useQueryClient(); + + return useMutation( + async (customerInfo) => { + const response = await privateAxios.post("/orders/", customerInfo, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${auth?.access}`, + }, + }); + return response.data; + }, + { + onSuccess: (data) => { + console.log("Added to cart: ", data); + queryClient.invalidateQueries(["productsInCart"]); + }, + onError: (error) => { + // eslint-disable-next-line no-console + console.error("Unable to add availability"); + if (error.response.status === 401) { + logout(); + } + }, + } + ); +}; + +export default useMakeOrder; diff --git a/src/hooks/Queries/blog/useBlogData.jsx b/src/hooks/Queries/blog/useBlogData.jsx index 578a86b6..9412b68d 100644 --- a/src/hooks/Queries/blog/useBlogData.jsx +++ b/src/hooks/Queries/blog/useBlogData.jsx @@ -1,7 +1,6 @@ import axios from "axios"; import { useQuery } from "@tanstack/react-query"; - const fetchBlogData = async (title_slug) => { try { const response = await axios.get( diff --git a/src/hooks/Queries/shop/useCartProducts.jsx b/src/hooks/Queries/shop/useCartProducts.jsx index 56b1d6ed..91b106a2 100644 --- a/src/hooks/Queries/shop/useCartProducts.jsx +++ b/src/hooks/Queries/shop/useCartProducts.jsx @@ -1,25 +1,36 @@ // https://apis.spaceyatech.com/api/cart/swaggs/ -import axios from "axios"; import { useQuery } from "@tanstack/react-query"; +import privateAxios from "../../../api/privateAxios"; const fetchProductsInCart = async () => { + const authObject = JSON.parse(localStorage.getItem("auth")) || {}; + const { access } = authObject; + try { - const response = await axios.get( - `${process.env.REACT_APP_API_BASE_URL}/cart/swaggs/` - ); + const response = await privateAxios.get("/cart/swaggs/", { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${access}`, + }, + }); + console.log("fetchProductsInCart: ", response.data); // !!! Image doesn't have a prefixed URL domain i.e it comes as "/media/product_images/main-sample_copy.png" instead of "https://apis.spaceyatech.com/media/product_images/main-sample_copy.png" + return response.data; } catch (error) { console.error("Error fetching products in cart: ", error); + if (error.response.status === 401) { + localStorage.removeItem("auth"); + } throw error; } }; -const useProductsInCart = () => { - return useQuery({ +const useProductsInCart = () => + // eslint-disable-next-line implicit-arrow-linebreak + useQuery({ queryKey: ["productsInCart"], queryFn: () => fetchProductsInCart(), refetchOnWindowFocus: true, }); -}; export default useProductsInCart; diff --git a/src/hooks/Queries/shop/useOrdersList.jsx b/src/hooks/Queries/shop/useOrdersList.jsx index dd72f8e4..a6e0924d 100644 --- a/src/hooks/Queries/shop/useOrdersList.jsx +++ b/src/hooks/Queries/shop/useOrdersList.jsx @@ -1,12 +1,10 @@ // https://apis.spaceyatech.com/api/orders// -import axios from "axios"; import { useQuery } from "@tanstack/react-query"; +import publicAxios from "../../../api/publicAxios"; const fetchOrders = async () => { try { - const response = await axios.get( - `${process.env.REACT_APP_API_BASE_URL}/orders/` - ); + const response = await publicAxios.get("/orders/"); return response.data; } catch (error) { console.error("Error fetching order summary: ", error); @@ -14,35 +12,42 @@ const fetchOrders = async () => { } }; -const useOrderSummary = () => { - return useQuery({ +const useOrderSummary = () => + useQuery({ queryKey: ["orders"], - queryFn: () => useSingleOrder(), + queryFn: () => fetchOrders(), refetchOnWindowFocus: true, staleTime: 5 * 60 * 60, }); -}; // https://apis.spaceyatech.com/api/orders/{orders_pk}/items/ const fetchSingleOrder = async (id) => { + const authObject = JSON.parse(localStorage.getItem("auth")) || {}; + const { access } = authObject; + try { - const response = await axios.get( - `${process.env.REACT_APP_API_BASE_URL}/orders/${id}/items/` - ); + const response = await publicAxios.get(`/orders/${id}/items/`, { + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${access}`, + }, + }); return response.data; } catch (error) { console.error("Error fetching order item: ", error); + if (error.response.status === 401) { + localStorage.removeItem("auth"); + } throw error; } }; -const useSingleOrder = (id) => { - return useQuery({ +const useSingleOrder = (id) => + useQuery({ queryKey: ["singleOrder"], queryFn: () => fetchSingleOrder(id), refetchOnWindowFocus: true, staleTime: 5 * 60 * 60, }); -}; export { useOrderSummary, useSingleOrder }; diff --git a/src/hooks/Queries/shop/useSwagList.jsx b/src/hooks/Queries/shop/useSwagList.jsx index 149a0988..243f6840 100644 --- a/src/hooks/Queries/shop/useSwagList.jsx +++ b/src/hooks/Queries/shop/useSwagList.jsx @@ -1,12 +1,12 @@ // https://apis.spaceyatech.com/api/swaggs/ -import axios from "axios"; import { useQuery } from "@tanstack/react-query"; +import publicAxios from "../../../api/publicAxios"; const fetchSwag = async () => { try { - const response = await axios.get( - `${process.env.REACT_APP_API_BASE_URL}/swaggs/` - ); + const response = await publicAxios.get("/swaggs/"); + console.log("response.data", response.data); + return response.data; } catch (error) { console.error("Error fetching swag list: ", error); @@ -14,12 +14,28 @@ const fetchSwag = async () => { } }; -const useSwagList = () => { - return useQuery({ +const useSwagList = () => + useQuery({ queryKey: ["swagList"], queryFn: () => fetchSwag(), refetchOnWindowFocus: false, }); + +const fetchSingleSwag = async (id) => { + try { + const response = await publicAxios.get(`/swaggs/${id}`); + return response.data; + } catch (error) { + console.error("Error fetching swag list: ", error); + throw error; + } }; -export default useSwagList; +const useSingleSwag = (id) => + useQuery({ + queryKey: ["single swag"], + queryFn: () => fetchSingleSwag(id), + refetchOnWindowFocus: false, + }); + +export { useSwagList, useSingleSwag }; diff --git a/src/hooks/useAuth.jsx b/src/hooks/useAuth.jsx index 2ee2e2f1..1de9b451 100644 --- a/src/hooks/useAuth.jsx +++ b/src/hooks/useAuth.jsx @@ -1,5 +1,5 @@ -import { useContext } from 'react'; -import { AuthContext } from '../utils/AuthContext'; +import { useContext } from "react"; +import { AuthContext } from "../context/AuthContext"; const useAuth = () => useContext(AuthContext); diff --git a/src/main.jsx b/src/main.jsx index 01e98260..ec922327 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,13 +1,13 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import React from "react"; import ReactDOM from "react-dom/client"; import { RouterProvider } from "react-router-dom"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; - import "./index.css"; -import router from "./router"; +import { ErrorBoundary } from "./APP"; +import { AuthContextProvider } from "./context/AuthContext"; import { SearchBlogProvider } from "./context/searchBlog"; -import { AuthContextProvider } from "./utils/AuthContext"; +import router from "./router"; const queryClient = new QueryClient({ defaultOptions: { @@ -19,13 +19,15 @@ const queryClient = new QueryClient({ ReactDOM.createRoot(document.getElementById("root")).render( - - - - - - - - + + + + + + + + + + ); diff --git a/src/router/index.jsx b/src/router/index.jsx index 1b58ace9..d37d8a35 100644 --- a/src/router/index.jsx +++ b/src/router/index.jsx @@ -1,5 +1,3 @@ -/* eslint-disable react/react-in-jsx-scope */ -/* eslint-disable quotes */ import { createBrowserRouter } from "react-router-dom"; import { @@ -12,35 +10,22 @@ import { Blogs, EventsPage, IndividualChapter, - Categories, - DonatePage, Homepage, Resources, ProductDisplay, + ForgotPassword, + LogIn, + ResetPassword, + SignUp, + CategoriesProducts, Checkout, SingleEvent, - SingleProductDonation, Error500, Error404, Error400, Error403, GalleryPage, } from "../APP"; -// import { -// AllChaptersPage, -// AdminLayout, -// AllEventsPage, -// AddChapterPage, -// AddEventPage, -// UpdateEventPage, -// } from "../ADMIN"; -// import { -// ForgotPassword, -// LogIn, -// ResetPassword, -// SignUp, -// Validate, -// } from "../AUTH"; const router = createBrowserRouter([ { @@ -72,7 +57,7 @@ const router = createBrowserRouter([ element: , }, { - path: "/blogs/:title_slug", + path: "/blogs/:titleSlug", element: , }, { @@ -90,7 +75,7 @@ const router = createBrowserRouter([ }, { path: "/shop/category/:category", - element: , + element: , }, { path: "/shop/item/:id", @@ -108,7 +93,6 @@ const router = createBrowserRouter([ path: "/resources", element: , }, - // { // path: "/donate", // element: , @@ -118,7 +102,6 @@ const router = createBrowserRouter([ // element: , // }, // { - // path: "/signup", // element: , // }, @@ -126,6 +109,22 @@ const router = createBrowserRouter([ // path: "/login", // element: , // }, + { + path: "/login", + element: , + }, + { + path: "/register", + element: , + }, + { + path: "/forgot-password", + element: , + }, + { + path: "/reset-password", + element: , + }, ], }, { @@ -179,32 +178,6 @@ const router = createBrowserRouter([ // }, // ], // }, - // { - // path: "/auth", - // element: , - // children: [ - // { - // path: "/auth/login", - // element: , - // }, - // { - // path: "/auth/signup", - // element: , - // }, - // { - // path: "/auth/validate", - // element: , - // }, - // { - // path: "/auth/forgot-password", - // element: , - // }, - // { - // path: "/auth/reset-password", - // element: , - // }, - // ], - // }, ]); export default router;