From 982baa276df4debe90b80738bf14da899cb78628 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Wed, 7 Aug 2024 15:12:34 +0300 Subject: [PATCH 01/46] fix: use product slug as URL for single product item --- src/components/shop/ProductCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/shop/ProductCard.jsx b/src/components/shop/ProductCard.jsx index eb320ed9..8b80dbc5 100644 --- a/src/components/shop/ProductCard.jsx +++ b/src/components/shop/ProductCard.jsx @@ -11,7 +11,7 @@ function ProductCard({ product }) { return (
Date: Wed, 7 Aug 2024 15:14:04 +0300 Subject: [PATCH 02/46] chore: add slug to ProductCard types --- src/components/shop/ProductCard.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/shop/ProductCard.jsx b/src/components/shop/ProductCard.jsx index 8b80dbc5..480e7215 100644 --- a/src/components/shop/ProductCard.jsx +++ b/src/components/shop/ProductCard.jsx @@ -72,5 +72,6 @@ ProductCard.propTypes = { image: PropTypes.string, stock: PropTypes.number.isRequired, category: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired, }).isRequired, }; From de7275c3704386285432fbe8f173519ef519711b Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 09:54:20 +0300 Subject: [PATCH 03/46] fix: use slug as product URL --- src/pages/shop/sections/PopularItemsSection.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/shop/sections/PopularItemsSection.jsx b/src/pages/shop/sections/PopularItemsSection.jsx index 50a6d03b..cacdc860 100644 --- a/src/pages/shop/sections/PopularItemsSection.jsx +++ b/src/pages/shop/sections/PopularItemsSection.jsx @@ -24,16 +24,16 @@ function PopularItemsSection() {
{products ?.slice(0, 8) - .map(({ stock, category, id, name, image, price }) => ( + .map(({ stock, category, id, name, image, price, slug }) => (
navigate(`/shop/item/${id}`)} + onClick={() => navigate(`/shop/item/${slug}`)} role="button" tabIndex={0} onKeyPress={(event) => { if (event.key === "Enter" || event.key === " ") { - navigate(`/shop/item/${id}`); + navigate(`/shop/item/${slug}`); } }} > From dfc6534714f033158adc901fc57bdbf54951b1cf Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 11:54:23 +0300 Subject: [PATCH 04/46] fix: update api URL in useSwagList hook --- src/hooks/Queries/shop/useSwagList.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks/Queries/shop/useSwagList.jsx b/src/hooks/Queries/shop/useSwagList.jsx index a526f7ae..d06ad112 100644 --- a/src/hooks/Queries/shop/useSwagList.jsx +++ b/src/hooks/Queries/shop/useSwagList.jsx @@ -5,7 +5,7 @@ import publicAxios from "../../../api/publicAxios"; const fetchSwag = async () => { try { - const response = await publicAxios.get("/swaggs/"); + const response = await publicAxios.get("/swaggsnew/"); return response.data; } catch (error) { @@ -23,7 +23,7 @@ const useSwagList = () => const fetchSingleSwag = async (id) => { try { - const response = await publicAxios.get(`/swaggs/${id}`); + const response = await publicAxios.get(`/swaggsnew/${id}`); return response.data; } catch (error) { toast.error("Error fetching swag list"); From 4b172c64c1c3ec70008b32b10ac2d74fbc95b3f2 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 11:57:58 +0300 Subject: [PATCH 05/46] feat: integrate api endpoints --- src/pages/shop/SingleItemPage.jsx | 192 +++++++++++++++++------------- 1 file changed, 112 insertions(+), 80 deletions(-) diff --git a/src/pages/shop/SingleItemPage.jsx b/src/pages/shop/SingleItemPage.jsx index 4e0a8481..ff3221bd 100644 --- a/src/pages/shop/SingleItemPage.jsx +++ b/src/pages/shop/SingleItemPage.jsx @@ -23,34 +23,45 @@ export default function SingleItemPage() { const [count, setCount] = useState(1); const [open, setOpen] = useState(false); const [selectedSize, setSelectedSize] = useState(null); - const [selectedColor, setSelectedColor] = useState(null); - const [Payload, setPayload] = useState({}); + const [selectedColor, setSelectedColor] = useState(""); + const [payload, setPayload] = useState({}); + + const [selectedVariant, setSelectedVariant] = useState(null); + const [variants, setVariants] = useState(null); + const [selectedImage, setSelectedImage] = useState(1); // const { data: singleOrder } = useSingleOrder(params.id); const { data: singleSwag, isSuccess, refetch } = useSingleSwag(params.id); const { mutate: addItemsToCart } = useAddSwagToCart(); - const sizes = ["XS", "S", "M", "L", "XL", "XXL"]; - const colors = ["BBD278", "BBC1F8", "FFD3F8", "AF674F"]; + const sizes = { + XS: "Extra Small", + S: "Small", + M: "Medium", + L: "Large", + XL: "Extra Large", + }; useEffect(() => { - localStorage.setItem("swagList", []); if (isSuccess) { - setPayload({ - swagg_id: singleSwag.id, - product: { - name: singleSwag.name, - description: singleSwag.description, - price: singleSwag.price, - size: selectedSize, - image: singleSwag.image, - }, - quantity: count, - }); + const initialVariant = singleSwag.attributes[0]; + setSelectedVariant(initialVariant); + setSelectedColor(initialVariant.color); + setVariants(singleSwag.attributes); } + }, [isSuccess, singleSwag]); + + useEffect(() => { refetch(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [params.id]); + }, [params.id, refetch]); + + useEffect(() => { + // Update selected variant when selected color changes + if (selectedColor && variants) { + const variant = variants.find((v) => v.color === selectedColor); + setSelectedVariant(variant); + } + }, [selectedColor, variants]); const addToLocalStorage = () => { let swagList = []; @@ -60,11 +71,11 @@ export default function SingleItemPage() { swagList = JSON.parse(localStorage.getItem("swagList")); // Also check for matching swagg_id that already exist in local storage - swagList.push(Payload); + swagList.push(payload); swagListJSON = JSON.stringify(swagList); localStorage.setItem("swagList", swagListJSON); } else { - swagList.push(Payload); + swagList.push(payload); swagListJSON = JSON.stringify(swagList); localStorage.setItem("swagList", swagListJSON); @@ -73,7 +84,7 @@ export default function SingleItemPage() { const handleAddToCart = () => { // Send to backend not giving a usable response - addItemsToCart(Payload); + addItemsToCart(payload); // Add to local storage addToLocalStorage(); @@ -83,7 +94,7 @@ export default function SingleItemPage() { // const handleBuyNow = () => { // // Send to backend not giving a usable response - // addItemsToCart(Payload); + // addItemsToCart(payload); // // Add to local storage // addToLocalStorage(); @@ -91,6 +102,9 @@ export default function SingleItemPage() { // navigate("/shop/checkout"); // }; + const getSizeKeyByValue = (value) => + Object.keys(sizes).find((key) => sizes[key] === value); + return ( <>
- -
- {VariationData.map((pic) => ( -
- -
+
+ {VariationData.map((image, index) => ( + + ))} +
+
+ {VariationData.map((pic, index) => ( + ))}

- {singleSwag.name} + {singleSwag?.name}

- KES {formatPrice(singleSwag.price)} + KES {formatPrice(singleSwag?.price)}

4.5 @@ -139,59 +167,63 @@ export default function SingleItemPage() {

- {colors.map((color) => ( -
- ) : ( -
- )} - - ))} + ) : ( +
+ )} + + ))}

Choose a size

- {sizes.map((size) => ( - - ))} + {selectedVariant && + selectedVariant?.size.map((size) => ( + + ))}

@@ -216,7 +248,7 @@ export default function SingleItemPage() { Product description

- {singleSwag.description} + {singleSwag?.description}

From f11dcdc947f468200bbbc3128b1b4f9393a3b94d Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 15:39:30 +0300 Subject: [PATCH 06/46] fix: properly add selected item to localstorage --- src/pages/shop/SingleItemPage.jsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/pages/shop/SingleItemPage.jsx b/src/pages/shop/SingleItemPage.jsx index ff3221bd..373f2a25 100644 --- a/src/pages/shop/SingleItemPage.jsx +++ b/src/pages/shop/SingleItemPage.jsx @@ -24,7 +24,6 @@ export default function SingleItemPage() { const [open, setOpen] = useState(false); const [selectedSize, setSelectedSize] = useState(null); const [selectedColor, setSelectedColor] = useState(""); - const [payload, setPayload] = useState({}); const [selectedVariant, setSelectedVariant] = useState(null); const [variants, setVariants] = useState(null); @@ -64,6 +63,13 @@ export default function SingleItemPage() { }, [selectedColor, variants]); const addToLocalStorage = () => { + const newData = { + name: singleSwag.name, + size: selectedSize.name, + color: selectedColor, + price: singleSwag.price, + orderUnits: count, + }; let swagList = []; let swagListJSON; if (localStorage.getItem("swagList")) { @@ -71,11 +77,11 @@ export default function SingleItemPage() { swagList = JSON.parse(localStorage.getItem("swagList")); // Also check for matching swagg_id that already exist in local storage - swagList.push(payload); + swagList.push(newData); swagListJSON = JSON.stringify(swagList); localStorage.setItem("swagList", swagListJSON); } else { - swagList.push(payload); + swagList.push(newData); swagListJSON = JSON.stringify(swagList); localStorage.setItem("swagList", swagListJSON); @@ -83,9 +89,6 @@ export default function SingleItemPage() { }; const handleAddToCart = () => { - // Send to backend not giving a usable response - addItemsToCart(payload); - // Add to local storage addToLocalStorage(); From 43194c55da22f0f750e7130b39da2130a1aeffe7 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:06:54 +0300 Subject: [PATCH 07/46] fix: add id, category, slug and image to cart data --- src/pages/shop/SingleItemPage.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/shop/SingleItemPage.jsx b/src/pages/shop/SingleItemPage.jsx index 373f2a25..da3d32ce 100644 --- a/src/pages/shop/SingleItemPage.jsx +++ b/src/pages/shop/SingleItemPage.jsx @@ -64,12 +64,17 @@ export default function SingleItemPage() { const addToLocalStorage = () => { const newData = { + id: singleSwag.id, name: singleSwag.name, + category: singleSwag.category, size: selectedSize.name, color: selectedColor, price: singleSwag.price, orderUnits: count, + slug: singleSwag.slug, + image: selectedVariant.images[0].image, }; + let swagList = []; let swagListJSON; if (localStorage.getItem("swagList")) { From 8b6d6366419106f605913748512e8f1f98299902 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:09:50 +0300 Subject: [PATCH 08/46] fix: multiply cart quantity by price of one unit --- src/pages/shop/SingleItemPage.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/shop/SingleItemPage.jsx b/src/pages/shop/SingleItemPage.jsx index da3d32ce..02c011f8 100644 --- a/src/pages/shop/SingleItemPage.jsx +++ b/src/pages/shop/SingleItemPage.jsx @@ -69,7 +69,7 @@ export default function SingleItemPage() { category: singleSwag.category, size: selectedSize.name, color: selectedColor, - price: singleSwag.price, + price: singleSwag.price * count, orderUnits: count, slug: singleSwag.slug, image: selectedVariant.images[0].image, From aa071fb40d4c050754ed27337839971367a44159 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:13:43 +0300 Subject: [PATCH 09/46] feat: display cart products from local storage --- src/components/shop/CartDrawer.jsx | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/src/components/shop/CartDrawer.jsx b/src/components/shop/CartDrawer.jsx index 011383d9..dd31ef1d 100644 --- a/src/components/shop/CartDrawer.jsx +++ b/src/components/shop/CartDrawer.jsx @@ -167,11 +167,8 @@ function CartDrawer({ open, setOpen }) {
    {/* {isSuccess && */} - {dummyCartData?.length > 0 && - (dummyCartData?.cart_items - ? dummyCartData.cart_items - : dummyCartData - )?.map((cartProduct) => ( + {cartProducts?.length > 0 && + cartProducts?.map((cartProduct) => (
@@ -197,7 +190,7 @@ function CartDrawer({ open, setOpen }) {

- Hoodies + {cartProduct.category}

@@ -205,12 +198,10 @@ function CartDrawer({ open, setOpen }) { {" "} - {cartProduct.name || - cartProduct.product?.name} + {cartProduct.name}

@@ -219,10 +210,7 @@ function CartDrawer({ open, setOpen }) { type="button" className="flex justify-end" onClick={() => { - handleDeleteSwag( - cartProduct.id || - cartProduct.swagg_id - ); + handleDeleteSwag(cartProduct.id); }} > {/* Delete icon */} @@ -230,7 +218,7 @@ function CartDrawer({ open, setOpen }) {
-

Qty: 1

+

Qty: {cartProduct.orderUnits}

KES {formatPrice(cartProduct.price)}

From 723b3b1f36535f3a337cadfb2ee95dbc68475e3d Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:14:55 +0300 Subject: [PATCH 10/46] fix: delete dummy cart products --- src/components/shop/CartDrawer.jsx | 39 ------------------------------ 1 file changed, 39 deletions(-) diff --git a/src/components/shop/CartDrawer.jsx b/src/components/shop/CartDrawer.jsx index dd31ef1d..7ee348b9 100644 --- a/src/components/shop/CartDrawer.jsx +++ b/src/components/shop/CartDrawer.jsx @@ -14,45 +14,6 @@ function CartDrawer({ open, setOpen }) { // Get the JSON string from localStorage const [cartProducts, setCartProducts] = useState([]); - const dummyCartData = [ - { - id: "271fcc1c-0337-44f4-9449-2bc35b6ffd01", - name: "Cityscape Jacket", - description: - "Introducing our Cityscape Jacket: a blend of urban flair and unbeatable comfort. Crafted with premium materials, it offers sleek design and weather resistance for city adventures. Stay stylish and protected with adjustable features and convenient pockets. Upgrade your urban wardrobe today!", - category: "Jackets", - image: - "https://apis.spaceyatech.com/media/product_images/main-sample_copy_Fud5OzF.png", - price: "3000.00", - stock: 10, - color: "brown", - }, - { - id: "232437b9-3e64-4cad-a6c3-08158e118207", - name: "Cityscape Jacket - Mid", - description: - "Introducing our Cityscape Jacket: a blend of urban flair and unbeatable comfort. Crafted with premium materials, it offers sleek design and weather resistance for city adventures. Stay stylish and protected with adjustable features and convenient pockets. Upgrade your urban wardrobe today!", - category: "Jackets", - image: - "https://apis.spaceyatech.com/media/product_images/main-sample_copy_BRv17MK.png", - price: "1800.00", - stock: 11, - color: "brown", - }, - { - id: "9cd9a601-0ed9-4685-8633-4b04e0811fc7", - name: "SYT Hoodie", - description: - "Unleash your tech-savvy style with our Tech-Fit Hoodie. Designed for the modern individual, it seamlessly integrates functionality and fashion. Crafted with cutting-edge materials, it offers unrivaled comfort and durability. Elevate your wardrobe with this essential piece that effortlessly combines innovation and style.", - category: "Hoodies", - image: - "https://apis.spaceyatech.com/media/product_images/sample1_copy_PXgn3MX.png", - price: "2000.00", - stock: 10, - color: "white", - }, - ]; - useEffect(() => { const storage = localStorage.getItem("swagList") ? JSON.parse(localStorage.getItem("swagList")) From d26b36f1d08d6801e38d8fa0d1183c8fe67c45e7 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:48:44 +0300 Subject: [PATCH 11/46] fix: delete from cart functionality --- src/components/shop/CartDrawer.jsx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/components/shop/CartDrawer.jsx b/src/components/shop/CartDrawer.jsx index 7ee348b9..53e16d40 100644 --- a/src/components/shop/CartDrawer.jsx +++ b/src/components/shop/CartDrawer.jsx @@ -45,9 +45,7 @@ function CartDrawer({ open, setOpen }) { // Parse it back to an array of objects const swagList = cartProducts; - const idxToDelete = swagList.findIndex( - (swag) => swag.swagg_id === cartItemId - ); + const idxToDelete = swagList.findIndex((swag) => swag.id === cartItemId); // Check if the object was found if (idxToDelete !== -1) { @@ -55,10 +53,11 @@ function CartDrawer({ open, setOpen }) { swagList.splice(idxToDelete, 1); // Convert the updated list to a JSON string - setCartProducts(JSON.stringify(swagList)); + const swagListJSON = JSON.stringify(swagList); + setCartProducts(swagList); // Store the updated list back to localStorage - localStorage.setItem("swagList", cartProducts); + localStorage.setItem("swagList", swagListJSON); } }; From 9339f0a6c045e175b3c5430073579c9af93d5b9c Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 16:53:03 +0300 Subject: [PATCH 12/46] refactor: item quantity button --- src/components/shop/Counter.jsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/shop/Counter.jsx b/src/components/shop/Counter.jsx index 21176814..b9699c16 100644 --- a/src/components/shop/Counter.jsx +++ b/src/components/shop/Counter.jsx @@ -1,13 +1,17 @@ import PropTypes from "prop-types"; function Counter({ className, setCount, count }) { + const increment = () => setCount((prevCount) => prevCount + 1); + const decrement = () => + setCount((prevCount) => (prevCount > 1 ? prevCount - 1 : prevCount)); + console.log(count) return (
@@ -21,7 +25,7 @@ function Counter({ className, setCount, count }) { type="button" data-action="increment" className="cursor-pointer outline-none w-20 border-y border-r border-r-[#EAECF0] border-y-[#EAECF0] rounded-r-md border-l" - onClick={() => setCount(count + 1)} + onClick={increment} > + From 05b2272f345342e3889d81214d052d4c144a05cc Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 20:02:11 +0300 Subject: [PATCH 13/46] feat: add more dummy images --- src/pages/shop/SingleItemPage.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/pages/shop/SingleItemPage.jsx b/src/pages/shop/SingleItemPage.jsx index 02c011f8..c5726624 100644 --- a/src/pages/shop/SingleItemPage.jsx +++ b/src/pages/shop/SingleItemPage.jsx @@ -15,7 +15,15 @@ import { useSingleSwag } from "../../hooks/Queries/shop/useSwagList"; import formatPrice from "../../utilities/formatPrice"; import ItemHeader from "./sections/ItemHeader"; -const VariationData = [SmallSample1, SmallSample2, SmallSample1, SmallSample2]; +const VariationData = [ + SmallSample1, + SmallSample2, + SmallSample1, + SmallSample2, + SmallSample2, + SmallSample1, + SmallSample2, +]; export default function SingleItemPage() { const params = useParams(); From 3bfb852a94843c0694c5ee3157fe04cfc7888257 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Thu, 8 Aug 2024 20:15:44 +0300 Subject: [PATCH 14/46] fix: linting issues --- src/components/shop/Counter.jsx | 2 +- src/pages/shop/SingleItemPage.jsx | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/shop/Counter.jsx b/src/components/shop/Counter.jsx index b9699c16..ae2f3796 100644 --- a/src/components/shop/Counter.jsx +++ b/src/components/shop/Counter.jsx @@ -4,7 +4,7 @@ function Counter({ className, setCount, count }) { const increment = () => setCount((prevCount) => prevCount + 1); const decrement = () => setCount((prevCount) => (prevCount > 1 ? prevCount - 1 : prevCount)); - console.log(count) + // console.log(count) return (
{showNavlinks ? (
); } diff --git a/src/pages/shop/Homepage.jsx b/src/pages/shop/Homepage.jsx index 3ed1e2b1..adaa2637 100644 --- a/src/pages/shop/Homepage.jsx +++ b/src/pages/shop/Homepage.jsx @@ -1,4 +1,6 @@ +import { useState } from "react"; import SeoMetadata from "../../components/SeoMetadata"; +import CartDrawer from "../../components/shop/CartDrawer"; import CartIcon from "../../components/shop/CartIcon"; import Banner from "./sections/Banner/index"; import BrowseProducts from "./sections/BrowseProducts"; @@ -7,6 +9,8 @@ import FilterSection from "./sections/FilterSection"; import NewProducts from "./sections/NewArrivals"; function Homepage() { + const [open, setOpen] = useState(false); + return ( <>
- +
@@ -28,6 +38,7 @@ function Homepage() {
+ ); } diff --git a/src/pages/shop/sections/ItemHeader.jsx b/src/pages/shop/sections/ItemHeader.jsx index 155ff9a9..23091ebe 100644 --- a/src/pages/shop/sections/ItemHeader.jsx +++ b/src/pages/shop/sections/ItemHeader.jsx @@ -5,7 +5,9 @@ // import { Fragment, useState } from "react"; // import { useSwagList } from "../../../hooks/Queries/shop/useSwagList"; import PropTypes from "prop-types"; +import { useState } from "react"; import { useLocation, useParams } from "react-router-dom"; +import CartDrawer from "../../../components/shop/CartDrawer"; import CartIcon from "../../../components/shop/CartIcon"; import SectionWrapper from "../../../components/shop/SectionWrapper"; @@ -13,6 +15,7 @@ import SectionWrapper from "../../../components/shop/SectionWrapper"; function ItemHeader({ show }) { const { pathname } = useLocation(); const { id } = useParams(); + const [open, setOpen] = useState(false); const pathnames = pathname .split("/") @@ -66,7 +69,14 @@ function ItemHeader({ show }) { - + + {/* Search box */} {/*
From 96b16937b5d53564da7f332b99cedb6f01b8e5b9 Mon Sep 17 00:00:00 2001 From: alvyynm Date: Sat, 10 Aug 2024 10:47:41 +0300 Subject: [PATCH 27/46] feat: prevent user from adding more items than available stock --- src/components/shop/Counter.jsx | 3 ++- src/pages/shop/SingleItemPage.jsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/shop/Counter.jsx b/src/components/shop/Counter.jsx index ae2f3796..6f00584a 100644 --- a/src/components/shop/Counter.jsx +++ b/src/components/shop/Counter.jsx @@ -1,6 +1,6 @@ import PropTypes from "prop-types"; -function Counter({ className, setCount, count }) { +function Counter({ className, setCount, count, maxStock }) { const increment = () => setCount((prevCount) => prevCount + 1); const decrement = () => setCount((prevCount) => (prevCount > 1 ? prevCount - 1 : prevCount)); @@ -22,6 +22,7 @@ function Counter({ className, setCount, count }) { {count}

+

+ {count} +

+ +