From 0c709c0dfd997de10b7d744f9e34a88de0beef31 Mon Sep 17 00:00:00 2001 From: Moaz Dawalibi Date: Sat, 22 Jun 2024 12:13:12 +0300 Subject: [PATCH] single product new design --- .../Products/ProductAdditionalInfo.tsx | 28 +++++ .../Products/ProductDescription.tsx | 14 +++ src/Components/Products/ProductInfo.tsx | 31 ++++++ src/Components/Products/ProductsTabs.tsx | 37 +++++++ src/Components/Setting/Tabs/ContactTab.tsx | 28 ++--- src/Pages/Contact/Contact.tsx | 37 +------ src/Pages/Product/Page.tsx | 90 ++++++++++++--- src/Styles/App/App.scss | 3 + src/Styles/App/Varibils.scss | 1 + src/Styles/Page/Product.scss | 104 ++++++++++++++---- src/translate/ar.json | 10 +- src/translate/ch.json | 10 +- src/translate/en.json | 9 +- 13 files changed, 308 insertions(+), 94 deletions(-) create mode 100644 src/Components/Products/ProductAdditionalInfo.tsx create mode 100644 src/Components/Products/ProductDescription.tsx create mode 100644 src/Components/Products/ProductInfo.tsx create mode 100644 src/Components/Products/ProductsTabs.tsx diff --git a/src/Components/Products/ProductAdditionalInfo.tsx b/src/Components/Products/ProductAdditionalInfo.tsx new file mode 100644 index 0000000..38026b8 --- /dev/null +++ b/src/Components/Products/ProductAdditionalInfo.tsx @@ -0,0 +1,28 @@ +import { useTranslation } from 'react-i18next'; +import { languageObject } from '../../utils/languageObject'; + +const ProductAdditionalInfo = (data:any) => { + const ProductAdditionalInfo = data?.data; + const { t } = useTranslation(); + const infoEntries = ProductAdditionalInfo?.info ? Object.entries(ProductAdditionalInfo.info) : []; + + console.log(ProductAdditionalInfo?.info); + + return ( +
+

{t("Additional Info")}

+ {infoEntries.length > 0 ? ( + infoEntries.map(([key, value]: [any, any]) => ( + +
{languageObject(key)}
+
{languageObject(value)}
+
+ )) + ) : ( +

{t("Product info is empty")}

+ )} +
+ ); +}; + +export default ProductAdditionalInfo; diff --git a/src/Components/Products/ProductDescription.tsx b/src/Components/Products/ProductDescription.tsx new file mode 100644 index 0000000..2c7f383 --- /dev/null +++ b/src/Components/Products/ProductDescription.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' + +const ProductDescription = ({data}:any) => { + const {t} = useTranslation(); + return ( +
+

{t("Description")}

+

{data?.description}

+
+ ) +} + +export default ProductDescription \ No newline at end of file diff --git a/src/Components/Products/ProductInfo.tsx b/src/Components/Products/ProductInfo.tsx new file mode 100644 index 0000000..597ee36 --- /dev/null +++ b/src/Components/Products/ProductInfo.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { useTranslation } from 'react-i18next' +import { languageObject } from '../../utils/languageObject'; + +const ProductInfo = (data:any) => { + + const productInfo = data?.data + const {t} = useTranslation(); + + return ( +
+

{t("Info")}

+
+ +
{t("name")}
{languageObject(productInfo?.name)}
+
+ +
{t("Category")}
{languageObject(productInfo?.category?.name)}
+
+ +
{t("Price")}
{productInfo?.price}
+
+ +
{t("Description")}
{languageObject(productInfo?.description)}
+
+
+
+ ) +} + +export default ProductInfo \ No newline at end of file diff --git a/src/Components/Products/ProductsTabs.tsx b/src/Components/Products/ProductsTabs.tsx new file mode 100644 index 0000000..cfe3b4b --- /dev/null +++ b/src/Components/Products/ProductsTabs.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Tabs } from 'antd'; +import type { TabsProps } from 'antd'; +import { useTranslation } from 'react-i18next'; +import ProductDescription from './ProductDescription'; +import ProductInfo from './ProductInfo'; +import ProductAdditionalInfo from './ProductAdditionalInfo'; + +const ProductTabs = ({data}:any) => { + + const {t} = useTranslation(); + console.log(data?.info); + + const items: TabsProps['items'] = [ + { + key: '1', + label: t('product_info'), + children: , + }, + { + key: '2', + label: t('description'), + children: , + }, + { + key: '3', + label: t('additional information'), + children: , + }, + ]; + + return ( + + ) +} + +export default ProductTabs \ No newline at end of file diff --git a/src/Components/Setting/Tabs/ContactTab.tsx b/src/Components/Setting/Tabs/ContactTab.tsx index abf2201..925c5f0 100644 --- a/src/Components/Setting/Tabs/ContactTab.tsx +++ b/src/Components/Setting/Tabs/ContactTab.tsx @@ -13,20 +13,20 @@ const ContactTab = ({className=""}: {className?: string }) => { const { t } = useTranslation(); const {mutate,isSuccess,isLoading} = useAddSupportMessage() - const SoicalInfo: { icon: any; text: string }[] = [ - { - icon: , - text: "info@yourdomain.com", - }, - { - icon: , - text: "+1 (378) 400-1234", - }, - { - icon: , - text: "www.yourdomain.com", - }, - ]; + const SoicalInfo: { icon: any; text: string }[] = [ + { + icon: , + text: "info@yourdomain.com", + }, + { + icon: , + text: "+1 (378) 400-1234", + }, + { + icon: , + text: "www.yourdomain.com", + }, + ]; const SoicalIcons: { icon: any; }[] = [ { diff --git a/src/Pages/Contact/Contact.tsx b/src/Pages/Contact/Contact.tsx index ba0697e..cd7679b 100644 --- a/src/Pages/Contact/Contact.tsx +++ b/src/Pages/Contact/Contact.tsx @@ -1,16 +1,10 @@ -import React, { useRef, useState } from 'react' -import { FaChevronRight, FaInstagram, FaLinkedinIn, FaTwitter } from "react-icons/fa6"; -import { Button, Form } from 'react-bootstrap'; +import React, { useState } from 'react' import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import { IoMailOutline,IoEarthOutline } from "react-icons/io5"; -import { FiPhone } from "react-icons/fi"; -import { FaFacebookF } from "react-icons/fa"; import ContactTab from '../../Components/Setting/Tabs/ContactTab'; import HeaderLink from '../../Components/Ui/HeaderLink'; const Contact = () => { - const form = useRef(null); const { t } = useTranslation(); const sendEmail = (e: React.FormEvent) => { @@ -35,35 +29,6 @@ const Contact = () => { const [Message , setMessage] = useState('') - const SoicalInfo: { icon: any; text: string }[] = [ - { - icon: , - text: "info@yourdomain.com", - }, - { - icon: , - text: "+1 (378) 400-1234", - }, - { - icon: , - text: "www.yourdomain.com", - }, - ]; - - const SoicalIcons: { icon: any; }[] = [ - { - icon: , - }, - { - icon: , - }, - { - icon: , - }, - { - icon: , - }, - ]; return (
diff --git a/src/Pages/Product/Page.tsx b/src/Pages/Product/Page.tsx index ed6f56f..69a9298 100644 --- a/src/Pages/Product/Page.tsx +++ b/src/Pages/Product/Page.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { IoIosArrowForward } from "react-icons/io"; import { Currency } from "../../Layout/app/Const"; @@ -12,15 +12,29 @@ import { languageObject } from "../../utils/languageObject"; import { jsonObjectToArray } from "../../utils/jsonObjectToArray"; import HeaderLink from "../../Components/Ui/HeaderLink"; import { BaseURL } from "../../api/utils/config"; +import ProductTabs from "../../Components/Products/ProductsTabs"; +import { IoHeartOutline } from "react-icons/io5"; +import { useAddFavorite, useDeleteFavorite } from "../../api/Favorite"; +import { useQueryClient } from "react-query"; +import { toast } from "react-toastify"; +import { FaCartPlus } from "react-icons/fa"; +import { useCartState } from "../../state/CartState"; +import useAuthState from "../../state/AuthState"; const Page = () => { const [t] = useTranslation(); const {product_id} = useParams() + const { Cart, setCart } = useCartState(); + const { isAuthenticated } = useAuthState(); + const { mutate: addToFavorite, isSuccess: Add } = useAddFavorite(); + const { mutate: deleteToFavorite, isSuccess: Delete } = useDeleteFavorite(); + const {data,isLoading} = useGetSingleBaseProduct({ show:product_id }) const product = data?.data?.product as Product || {} + console.log(product); const [MainImage, setMainImage] = useState(product?.main_photo); @@ -28,8 +42,51 @@ const Page = () => { const handelImage = (item: any) => { // setMainImage(item); }; - console.log(product?.images); - + + const queryClient = useQueryClient(); + + const handelChangeFavorite = (item: Product) => { + if (!isAuthenticated) { + toast.error("sorry you need to be authenticated"); + return; + } + if (item?.favorite) { + + deleteToFavorite({ + id: item?.id, + }); + } else { + addToFavorite({ + id: item?.id, + }); + } + queryClient.invalidateQueries('mainProduct'); + + }; + useEffect(() => { + if (Add) { + toast.success("added to favorite successfully"); + queryClient.invalidateQueries("mainProduct"); + } + }, [Add]); + useEffect(() => { + if (Delete) { + toast.success("removed from favorite successfully"); + queryClient.invalidateQueries("mainProduct"); + } + }, [Delete]); + const handelAddToCart = (item: Product) => { + if (!isAuthenticated) { + + toast.error("sorry you need to be authenticated"); + return; + } + setCart(item); + + + toast.success(`${languageObject(item?.name)}` + " added to cart"); + }; + const info = jsonObjectToArray(product?.info) if(isLoading){ @@ -43,13 +100,22 @@ const Page = () => {
+
+ handelChangeFavorite(product)} + className={product?.favorite ? "" : "not_favorite"} + /> + handelAddToCart(product)} + /> + {/* {t("add to cart")} */} +
{product?.images?.map((item,index)=>{ return ( handelImage(item?.path)} > @@ -68,20 +134,8 @@ const Page = () => {
-

{languageObject(product?.name)}

-
- - -
{t("Category")}
{languageObject(product?.category?.name)}
-
- -
{t("Price")}
{product?.price}
-
- -
{t("Description")}
{languageObject(product?.description)}
-
-
-
+ +
diff --git a/src/Styles/App/App.scss b/src/Styles/App/App.scss index eb972b4..1c5d659 100644 --- a/src/Styles/App/App.scss +++ b/src/Styles/App/App.scss @@ -57,3 +57,6 @@ svg { } } } + +// + diff --git a/src/Styles/App/Varibils.scss b/src/Styles/App/Varibils.scss index c04f39a..d04d9ae 100644 --- a/src/Styles/App/Varibils.scss +++ b/src/Styles/App/Varibils.scss @@ -9,6 +9,7 @@ --bg: white; --bg2: #dcdcdc; --white: white; + --whiteOpacity: #f6f6f6; --shadow: rgba(0, 0, 0, 0.15); --gray: rgb(207, 210, 214); --whiteGray: #717171; diff --git a/src/Styles/Page/Product.scss b/src/Styles/Page/Product.scss index 97cbb4e..5987119 100644 --- a/src/Styles/Page/Product.scss +++ b/src/Styles/Page/Product.scss @@ -21,6 +21,16 @@ .Product_left { display: flex; width: 45%; + .fav_icon{ + svg{ + font-size: 25px; + transition: ease-in-out .3s; + margin-inline: 5px; + &:hover{ + color: var(--primary); + } + } + } > span { display: flex; flex-direction: column; @@ -33,7 +43,6 @@ display: flex; gap: 20px; overflow-x: scroll; - // max-width: 40vw; &::-webkit-scrollbar { display: none; } @@ -50,34 +59,79 @@ } } - .Product_Right { + .Product_info { display: flex; flex-direction: column; - gap: 20px; + gap: 5px; align-items: flex-start; - padding-top: 2%; h1 { font-size: 4vw; } > div { + width: 100%; display: flex; flex-direction: column; - gap: 20px; + gap: 10px; > span { display: flex; align-items: flex-start; gap: 20px; width: 80%; + padding: 10px 15px 0px 15px; + &:nth-child(odd){ + background: var(--whiteOpacity) !important; + } h6 { - color: gray; + color: var(--whiteGray); position: relative; - min-width: 80px; + width: 50%; + font-size: 12px; + } + h5{ + height: 100%; + color: var(--text); + font-size: 12px; } } } } } - +.Product_Right{ + width: 50%; +} +.product_tab{ + width: 100%; +} +.product_description{ + width: 100%; + p{ + padding-inline: 15px; + width: 95%; + background: var(--whiteOpacity); + } +} +.product_additional_info{ + > span { + display: flex; + align-items: flex-start; + gap: 20px; + width: 100%; + padding: 10px 15px 0px 15px; + &:nth-child(even){ + background: var(--whiteOpacity) !important; + } + h6 { + color: var(--text); + position: relative; + width: 50%; + font-size: 10px; + } + h5{ + color: var(--text); + font-size: 10px; + } + } +} .Products { display: flex; flex-direction: column; @@ -89,23 +143,25 @@ > header { display: flex; justify-content: space-between; + h1{ + font-size: 20px; + } } .ProductCards { max-width: 90vw; margin-inline: auto; display: flex; - // gap: 40px; - // overflow-x: scroll; - - // &::-webkit-scrollbar { - // display: none; - // } } } - -@media screen and (max-width:700px) { +@media screen and (max-width:850px) { + .ant-tabs .ant-tabs-tab-btn{ + font-size: 14px !important; + } + .ant-tabs >.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs >div>.ant-tabs-nav .ant-tabs-nav-wrap{ + justify-content: center !important;align-items: center !important; + } .Product { display: flex; min-height: 100vh; @@ -121,24 +177,20 @@ gap: 10%; } .Product_left { - display: flex; - width: 90%; + display: flex;justify-content: center;align-items: center; + width: 100%; margin-inline: auto; > span { display: flex; flex-direction: column; gap: 20px; > img { - width: 60%; + width: 50%; margin-inline: auto; } .gallery_product { display: flex; gap: 20px; - // overflow-x: scroll; - // &::-webkit-scrollbar { - // display: none; - // } span { padding: 10px 10px; border: 2px solid rgba(128, 128, 128, 0.1); @@ -180,4 +232,10 @@ } } } +} + +@media screen and (max-width:450px) { + .ant-tabs >.ant-tabs-nav .ant-tabs-nav-wrap,.ant-tabs >div>.ant-tabs-nav .ant-tabs-nav-wrap{ + justify-content: flex-start !important;align-items: flex-start !important; + } } \ No newline at end of file diff --git a/src/translate/ar.json b/src/translate/ar.json index 42d127e..901c3cc 100755 --- a/src/translate/ar.json +++ b/src/translate/ar.json @@ -390,6 +390,14 @@ "Track, return or purchase items":"تتبع أو إرجاع أو شراء العناصر", "Order History":"تاريخ الطلب", "Please try using other keywords to find the product name":"يرجى محاولة استخدام كلمات رئيسية أخرى للعثور على اسم المنتج", -"There are no suitable products":"لا توجد منتجات مناسبة" +"There are no suitable products":"لا توجد منتجات مناسبة", + +"product_info":"معلومات المنتج", +"additional information":"معلومات إضافية", +"description":"الوصف", +"Info":"معلومات", +"Additional Info":"معلومات إضافية", +"Product info is empty":"معلومات المنتج فارغة", +"":"" } \ No newline at end of file diff --git a/src/translate/ch.json b/src/translate/ch.json index 1b39c00..0fd26bd 100644 --- a/src/translate/ch.json +++ b/src/translate/ch.json @@ -376,5 +376,13 @@ "Track, return or purchase items":"跟踪、退货或购买物品", "Order History":"订单历史", "Please try using other keywords to find the product name":"请尝试使用其他关键字来查找产品名称", - "There are no suitable products":"没有合适的产品" + "There are no suitable products":"没有合适的产品", + + "product_info":"产品信息", + "additional information":"附加信息", + "description":"描述", + "Info":"信息", + "Additional Info":"附加信息", + "Product info is empty":"产品信息为空", + "":"" } \ No newline at end of file diff --git a/src/translate/en.json b/src/translate/en.json index fb32c19..ad22726 100755 --- a/src/translate/en.json +++ b/src/translate/en.json @@ -387,7 +387,14 @@ "Track, return or purchase items":"Track, return or purchase items", "Order History":"Order History", "Please try using other keywords to find the product name":"Please try using other keywords to find the product name", - "There are no suitable products":"There are no suitable products" + "There are no suitable products":"There are no suitable products", + + "product_info":"Product info", + "additional information":"Additional information", + "description":"Description", + "Info":"Info", + "Additional Info":"Additional Info", + "Product info is empty":"Product info is empty" } \ No newline at end of file