├── npm ├── .eslintrc.json ├── sanity_onlineshop ├── plugins │ └── .gitkeep ├── .eslintrc ├── config │ ├── @sanity │ │ ├── data-aspects.json │ │ ├── vision.json │ │ ├── form-builder.json │ │ ├── default-layout.json │ │ └── default-login.json │ └── .checksums ├── static │ ├── .gitkeep │ └── favicon.ico ├── .npmignore ├── tsconfig.json ├── README.md ├── schemas │ ├── user.js │ └── schema.js ├── sanity.json └── package.json ├── public ├── images │ ├── logo.png │ ├── zishop.ico │ ├── zishopBanner.png │ ├── banners-img │ │ ├── home1.webp │ │ └── home2.webp │ ├── brand-logo-img │ │ ├── hp.webp │ │ ├── lg.webp │ │ ├── apple.webp │ │ ├── asus.webp │ │ ├── benq.webp │ │ ├── del.webp │ │ ├── dior.webp │ │ ├── gucci.webp │ │ ├── mac.webp │ │ ├── msi.webp │ │ ├── puma.webp │ │ ├── rolex.webp │ │ ├── sony.webp │ │ ├── adidas.webp │ │ ├── bvlgari.webp │ │ ├── loreal.webp │ │ ├── samsung.webp │ │ ├── toshiba.webp │ │ ├── versace.webp │ │ ├── xiaomi.webp │ │ ├── maybelline.webp │ │ ├── dolce-gabbana.webp │ │ ├── louis-vuitton.webp │ │ └── patek-philippe.webp │ ├── discount-icon │ │ └── discount.webp │ ├── slider-img │ │ ├── toy-banner.webp │ │ ├── beauty-banner.webp │ │ ├── digital-banner.webp │ │ ├── fashion-banner.webp │ │ ├── house-banner.webp │ │ └── stationery-banner.webp │ ├── benefit-icons │ │ ├── 007-return.webp │ │ ├── 004-headphones.webp │ │ ├── 006-best-seller.webp │ │ ├── 003-cash-on-delivery.webp │ │ └── 005-delivery-truck-2.webp │ ├── carouselBox-bg │ │ └── offersbg.webp │ ├── category-img │ │ ├── toy-category.webp │ │ ├── house-category.webp │ │ ├── sport-category.webp │ │ ├── beauty-category.webp │ │ ├── digital-category.webp │ │ ├── fashion-category.webp │ │ └── stationery-category.webp │ └── category-icon │ │ ├── toy-category.webp │ │ ├── beauty-category.webp │ │ ├── digital-category.webp │ │ ├── fashion-category.webp │ │ ├── house-category.webp │ │ ├── sport-category.webp │ │ └── stationery-category.webp ├── fonts │ ├── ttf │ │ ├── iranyekanwebblack.ttf │ │ ├── iranyekanwebbold.ttf │ │ ├── iranyekanweblight.ttf │ │ ├── iranyekanwebmedium.ttf │ │ ├── iranyekanwebthin.ttf │ │ ├── iranyekanwebregular.ttf │ │ ├── iranyekanwebextrablack.ttf │ │ └── iranyekanwebextrabold.ttf │ └── woff │ │ ├── iranyekanwebbold.woff │ │ ├── iranyekanwebthin.woff │ │ ├── iranyekanwebblack.woff │ │ ├── iranyekanweblight.woff │ │ ├── iranyekanwebmedium.woff │ │ ├── iranyekanwebextrabold.woff │ │ ├── iranyekanwebregular.woff │ │ └── iranyekanwebextrablack.woff ├── sitemap.xml └── robots.txt ├── lib ├── types │ ├── breadcrumb.ts │ ├── dropDown.ts │ ├── offerProductsState.ts │ ├── settingBox.ts │ ├── megaMenu.ts │ ├── categories.ts │ ├── favorite.ts │ ├── activeMenuItem.ts │ ├── productList.ts │ ├── sidebar.ts │ ├── user.ts │ ├── pagePathsParams.ts │ ├── cart.ts │ └── products.ts ├── config.ts └── client.ts ├── pages ├── blank.tsx ├── favorite.tsx ├── api │ ├── hello.ts │ └── users │ │ ├── login.ts │ │ └── register.ts ├── _app.tsx ├── cart.tsx ├── offers.tsx ├── _document.tsx ├── newestProducts.tsx ├── [category] │ ├── index.tsx │ └── [subCategory] │ │ ├── index.tsx │ │ └── [title] │ │ ├── index.tsx │ │ └── [slug] │ │ └── index.tsx ├── login.tsx ├── about.tsx ├── index.tsx └── signUp.tsx ├── mock ├── sortRadioInput.js ├── banner.js ├── benefits.js ├── slider.js ├── category-sm.js ├── footer.js ├── brand.js ├── menuItems.js └── category-lg.js ├── postcss.config.js ├── global.d.ts ├── next-sitemap.config.js ├── utilities ├── changeNumbersFormatEnToFa.ts ├── error.ts ├── calculateDiscountPercentage.ts ├── auth.ts ├── sortByPopularity.ts ├── currencyFormat.ts ├── sortByCost.ts └── sortByTimeStamp.ts ├── next-env.d.ts ├── hooks ├── useLanguage.tsx ├── useWindowDimensions.tsx ├── useCountdown.tsx └── useExchangeRateGBPToIRR.tsx ├── components ├── header │ ├── menu │ │ ├── index.tsx │ │ ├── SideBarMenu │ │ │ ├── index.tsx │ │ │ ├── SideNav.tsx │ │ │ ├── SideBar.tsx │ │ │ └── SideNavContent.tsx │ │ └── MegaMenu │ │ │ ├── index.tsx │ │ │ ├── ExtraMenu.tsx │ │ │ ├── MenusContainer.tsx │ │ │ ├── MegaMenu.tsx │ │ │ └── SubMenu.tsx │ ├── user │ │ ├── index.tsx │ │ ├── LoginBtn.tsx │ │ ├── UserAccountBtn.tsx │ │ └── UserAccountBox.tsx │ ├── Logo.tsx │ ├── SearchBar.tsx │ ├── theme │ │ ├── Theme.tsx │ │ └── ThemeItem.tsx │ ├── Settings.tsx │ ├── language │ │ ├── LanguageItem.tsx │ │ └── Language.tsx │ └── index.tsx ├── UI │ ├── discountCountdown │ │ ├── ExpiredNotice.tsx │ │ ├── DiscountCountdown.tsx │ │ ├── DateTimeDisplay.tsx │ │ └── ShowCounter.tsx │ ├── SectionTitle │ │ └── index.tsx │ ├── CarouselBox │ │ ├── CarouselBoxArrows.tsx │ │ ├── CarouselBoxCard.tsx │ │ └── CarouselBox.tsx │ ├── Input.tsx │ ├── Breadcrumb.tsx │ ├── card │ │ ├── CardActions.tsx │ │ └── Card.tsx │ └── DropDown.tsx ├── brands │ ├── BrandBox.tsx │ └── index.tsx ├── productDetails │ ├── SimilarProducts.tsx │ ├── index.tsx │ ├── ProductPageActions.tsx │ ├── ImageSection.tsx │ ├── DetailsSection.tsx │ └── CallToAction.tsx ├── cart │ ├── CartList.tsx │ ├── OrderSummaryBox.tsx │ └── CartIcon.tsx ├── Offers │ └── Offers.tsx ├── favorite │ ├── index.tsx │ └── FavoriteItem.tsx ├── Benefits │ └── index.tsx ├── carousel │ ├── Arrows.tsx │ ├── Slide.tsx │ └── index.tsx ├── footer │ ├── footerContent │ │ ├── FooterColumns.tsx │ │ └── SocialPart.tsx │ └── index.tsx ├── layout │ └── Layout.tsx ├── banners │ ├── index.tsx │ └── banner-box │ │ └── BannerBox.tsx ├── category │ ├── CategorySmBox.tsx │ ├── CategoryLgBox.tsx │ └── Category.tsx ├── newest │ └── Newest.tsx └── productList │ ├── SubmenuCategory.tsx │ ├── Sort.tsx │ └── ProductList.tsx ├── .gitignore ├── store ├── cartUI-slice.ts ├── megaMenu-slice.ts ├── newestProduct-slice.ts ├── settingBox-slice.ts ├── specialOfferProducts-slice.ts ├── user-slice.ts ├── activeMenuItem-slice.ts ├── favorite-slice.ts ├── sideNavBar-slice.ts ├── index.ts ├── sortedProductList-slice.ts └── cart-slice.ts ├── tsconfig.json ├── next.config.js ├── package.json └── tailwind.config.js /npm: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /sanity_onlineshop/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | User-specific packages can be placed here 2 | -------------------------------------------------------------------------------- /sanity_onlineshop/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@sanity/eslint-config-studio" 3 | } 4 | -------------------------------------------------------------------------------- /sanity_onlineshop/config/@sanity/data-aspects.json: -------------------------------------------------------------------------------- 1 | { 2 | "listOptions": {} 3 | } 4 | -------------------------------------------------------------------------------- /sanity_onlineshop/config/@sanity/vision.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultApiVersion": "2021-10-21" 3 | } 4 | -------------------------------------------------------------------------------- /public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/logo.png -------------------------------------------------------------------------------- /public/images/zishop.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/zishop.ico -------------------------------------------------------------------------------- /lib/types/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | export interface IBreadcrumb { 2 | breadcrumb: string; 3 | href: string; 4 | } 5 | -------------------------------------------------------------------------------- /sanity_onlineshop/config/@sanity/form-builder.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": { 3 | "directUploads": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /sanity_onlineshop/static/.gitkeep: -------------------------------------------------------------------------------- 1 | Files placed here will be served by the Sanity server under the `/static`-prefix 2 | -------------------------------------------------------------------------------- /public/images/zishopBanner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/zishopBanner.png -------------------------------------------------------------------------------- /lib/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | projectId: "3c4n15ly", 3 | dataset: "production", 4 | }; 5 | export default config; 6 | -------------------------------------------------------------------------------- /lib/types/dropDown.ts: -------------------------------------------------------------------------------- 1 | export interface IDropDown { 2 | title: string; 3 | url?: string; 4 | subtitles: string[]; 5 | } 6 | -------------------------------------------------------------------------------- /public/images/banners-img/home1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/banners-img/home1.webp -------------------------------------------------------------------------------- /public/images/banners-img/home2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/banners-img/home2.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/hp.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/hp.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/lg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/lg.webp -------------------------------------------------------------------------------- /sanity_onlineshop/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/sanity_onlineshop/static/favicon.ico -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebblack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebblack.ttf -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebbold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebbold.ttf -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanweblight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanweblight.ttf -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebmedium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebmedium.ttf -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebthin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebthin.ttf -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebbold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebbold.woff -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebthin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebthin.woff -------------------------------------------------------------------------------- /public/images/brand-logo-img/apple.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/apple.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/asus.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/asus.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/benq.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/benq.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/del.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/del.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/dior.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/dior.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/gucci.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/gucci.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/mac.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/mac.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/msi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/msi.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/puma.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/puma.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/rolex.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/rolex.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/sony.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/sony.webp -------------------------------------------------------------------------------- /sanity_onlineshop/config/@sanity/default-layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "toolSwitcher": { 3 | "order": [], 4 | "hidden": [] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebregular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebregular.ttf -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebblack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebblack.woff -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanweblight.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanweblight.woff -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebmedium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebmedium.woff -------------------------------------------------------------------------------- /public/images/brand-logo-img/adidas.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/adidas.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/bvlgari.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/bvlgari.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/loreal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/loreal.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/samsung.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/samsung.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/toshiba.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/toshiba.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/versace.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/versace.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/xiaomi.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/xiaomi.webp -------------------------------------------------------------------------------- /public/images/discount-icon/discount.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/discount-icon/discount.webp -------------------------------------------------------------------------------- /public/images/slider-img/toy-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/toy-banner.webp -------------------------------------------------------------------------------- /pages/blank.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Blank = () => { 4 | return
Blank
; 5 | }; 6 | 7 | export default Blank; 8 | -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebextrablack.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebextrablack.ttf -------------------------------------------------------------------------------- /public/fonts/ttf/iranyekanwebextrabold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/ttf/iranyekanwebextrabold.ttf -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebextrabold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebextrabold.woff -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebregular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebregular.woff -------------------------------------------------------------------------------- /public/images/benefit-icons/007-return.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/benefit-icons/007-return.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/maybelline.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/maybelline.webp -------------------------------------------------------------------------------- /public/images/carouselBox-bg/offersbg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/carouselBox-bg/offersbg.webp -------------------------------------------------------------------------------- /public/images/category-img/toy-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/toy-category.webp -------------------------------------------------------------------------------- /public/images/slider-img/beauty-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/beauty-banner.webp -------------------------------------------------------------------------------- /public/images/slider-img/digital-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/digital-banner.webp -------------------------------------------------------------------------------- /public/images/slider-img/fashion-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/fashion-banner.webp -------------------------------------------------------------------------------- /public/images/slider-img/house-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/house-banner.webp -------------------------------------------------------------------------------- /public/fonts/woff/iranyekanwebextrablack.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/fonts/woff/iranyekanwebextrablack.woff -------------------------------------------------------------------------------- /public/images/category-icon/toy-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/toy-category.webp -------------------------------------------------------------------------------- /public/images/category-img/house-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/house-category.webp -------------------------------------------------------------------------------- /public/images/category-img/sport-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/sport-category.webp -------------------------------------------------------------------------------- /mock/sortRadioInput.js: -------------------------------------------------------------------------------- 1 | export const radioBtnValue = [ 2 | "all", 3 | "newestProducts", 4 | "popular", 5 | "cheapest", 6 | "expensive", 7 | ]; 8 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-import": {}, 4 | tailwindcss: {}, 5 | autoprefixer: {}, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /public/images/benefit-icons/004-headphones.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/benefit-icons/004-headphones.webp -------------------------------------------------------------------------------- /public/images/benefit-icons/006-best-seller.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/benefit-icons/006-best-seller.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/dolce-gabbana.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/dolce-gabbana.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/louis-vuitton.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/louis-vuitton.webp -------------------------------------------------------------------------------- /public/images/brand-logo-img/patek-philippe.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/brand-logo-img/patek-philippe.webp -------------------------------------------------------------------------------- /public/images/category-icon/beauty-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/beauty-category.webp -------------------------------------------------------------------------------- /public/images/category-icon/digital-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/digital-category.webp -------------------------------------------------------------------------------- /public/images/category-icon/fashion-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/fashion-category.webp -------------------------------------------------------------------------------- /public/images/category-icon/house-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/house-category.webp -------------------------------------------------------------------------------- /public/images/category-icon/sport-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/sport-category.webp -------------------------------------------------------------------------------- /public/images/category-img/beauty-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/beauty-category.webp -------------------------------------------------------------------------------- /public/images/category-img/digital-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/digital-category.webp -------------------------------------------------------------------------------- /public/images/category-img/fashion-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/fashion-category.webp -------------------------------------------------------------------------------- /public/images/slider-img/stationery-banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/slider-img/stationery-banner.webp -------------------------------------------------------------------------------- /public/images/category-img/stationery-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-img/stationery-category.webp -------------------------------------------------------------------------------- /lib/types/offerProductsState.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "./products"; 2 | 3 | export interface IOfferProducts { 4 | specialOfferProducts: IProduct[] | []; 5 | } 6 | -------------------------------------------------------------------------------- /public/images/benefit-icons/003-cash-on-delivery.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/benefit-icons/003-cash-on-delivery.webp -------------------------------------------------------------------------------- /public/images/benefit-icons/005-delivery-truck-2.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/benefit-icons/005-delivery-truck-2.webp -------------------------------------------------------------------------------- /public/images/category-icon/stationery-category.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZahraMirzaei/online-shop/HEAD/public/images/category-icon/stationery-category.webp -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | interface Date { 2 | addDays: (days: number) => Date; 3 | } 4 | 5 | //to fix error that when I want to add function (addDays) to Date.prototype, happen. 6 | -------------------------------------------------------------------------------- /lib/types/settingBox.ts: -------------------------------------------------------------------------------- 1 | export interface ISettingBox { 2 | isOpen: boolean; 3 | } 4 | 5 | export interface ISettingBoxRootState { 6 | settingBox: ISettingBox; 7 | } 8 | -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | 3 | module.exports = { 4 | siteUrl: process.env.SITE_URL, 5 | generateRobotsTxt: true, 6 | }; 7 | -------------------------------------------------------------------------------- /lib/types/megaMenu.ts: -------------------------------------------------------------------------------- 1 | export interface IMegaMenuShow { 2 | isMegaMenuOpen: boolean; 3 | } 4 | 5 | export interface IMegaMenuRootState { 6 | megaMenu: IMegaMenuShow; 7 | } 8 | -------------------------------------------------------------------------------- /utilities/changeNumbersFormatEnToFa.ts: -------------------------------------------------------------------------------- 1 | export const changeNumbersFormatEnToFa = (number: number | string) => 2 | number.toString().replace(/\d/g, (index) => "۰۱۲۳۴۵۶۷۸۹"[index]); 3 | -------------------------------------------------------------------------------- /lib/types/categories.ts: -------------------------------------------------------------------------------- 1 | import { TSlug } from "./products"; 2 | 3 | export interface ICategory { 4 | title: string; 5 | slug: TSlug; 6 | description?: string; 7 | parents?: any; 8 | } 9 | -------------------------------------------------------------------------------- /lib/types/favorite.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "./products"; 2 | export interface IFavorite { 3 | items: IProduct[]; 4 | } 5 | 6 | export interface IFavoriteRootState { 7 | favorite: IFavorite; 8 | } 9 | -------------------------------------------------------------------------------- /sanity_onlineshop/config/@sanity/default-login.json: -------------------------------------------------------------------------------- 1 | { 2 | "providers": { 3 | "mode": "append", 4 | "redirectOnSingle": false, 5 | "entries": [] 6 | }, 7 | "loginMethod": "dual" 8 | } 9 | -------------------------------------------------------------------------------- /utilities/error.ts: -------------------------------------------------------------------------------- 1 | const getError = (err: any) => 2 | err.response && err.response.data && err.response.data.message 3 | ? err.response.data.message 4 | : err.message; 5 | 6 | export { getError }; 7 | -------------------------------------------------------------------------------- /utilities/calculateDiscountPercentage.ts: -------------------------------------------------------------------------------- 1 | export const calculateDiscountPercentage = ( 2 | price: number, 3 | discountPercentage: number 4 | ) => { 5 | return price - price * (discountPercentage * 0.01); 6 | }; 7 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /lib/types/activeMenuItem.ts: -------------------------------------------------------------------------------- 1 | export interface IActiveMenuItem { 2 | activeMenuItemIndex: number; 3 | activeMenuItemText: string; 4 | } 5 | 6 | export interface IActiveMenuItemRootState { 7 | activeMenuItem: IActiveMenuItem; 8 | } 9 | -------------------------------------------------------------------------------- /sanity_onlineshop/.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | /logs 3 | *.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | /coverage 7 | 8 | # Dependency directories 9 | node_modules 10 | 11 | # Compiled sanity studio 12 | /dist 13 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://zishop.vercel.app/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /lib/types/productList.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "./products"; 2 | 3 | export interface IProductList { 4 | productsList: IProduct[] | []; 5 | } 6 | 7 | export interface IProductListRootState { 8 | sortedProductsList: IProductList; 9 | } 10 | -------------------------------------------------------------------------------- /utilities/auth.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import { IUser } from "../lib/types/user"; 3 | 4 | export const signToken = (user: IUser) => { 5 | return jwt.sign(user, process.env.JWT_SECRET as string, { 6 | expiresIn: "30d", 7 | }); 8 | }; 9 | -------------------------------------------------------------------------------- /utilities/sortByPopularity.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "../lib/types/products"; 2 | 3 | export const sortByPoPularity = ( 4 | product1: IProduct, 5 | product2: IProduct 6 | ): number => { 7 | return product2.starRating - product1.starRating; 8 | }; 9 | -------------------------------------------------------------------------------- /lib/types/sidebar.ts: -------------------------------------------------------------------------------- 1 | import { IDropDown } from "./dropDown"; 2 | 3 | export interface ISideNavBar { 4 | isSidebarOpen: boolean; 5 | isNavbarOpen: boolean; 6 | dropDownList: IDropDown[]; 7 | } 8 | 9 | export interface ISideNavBarRootState { 10 | sideNavBar: ISideNavBar; 11 | } 12 | -------------------------------------------------------------------------------- /hooks/useLanguage.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from "next/router"; 2 | import en from "../locales/en"; 3 | import fa from "../locales/fa"; 4 | 5 | export const useLanguage = () => { 6 | const { locale } = useRouter(); 7 | const t = locale === "en" ? en : fa; 8 | return { t, locale }; 9 | }; 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | User-agent: Googlebot 6 | Disallow: /nogooglebot/ 7 | 8 | # Host 9 | Host: https://zishop.vercel.app/ 10 | 11 | # Sitemaps 12 | Sitemap: https://zishop.vercel.app/sitemap.xml 13 | Sitemap: https://zishop.vercel.app/sitemap-0.xml 14 | -------------------------------------------------------------------------------- /components/header/menu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SideBarMenu from "./SideBarMenu"; 3 | import MegaMenu from "./MegaMenu"; 4 | 5 | const index = () => { 6 | return ( 7 | <> 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | export default index; 15 | -------------------------------------------------------------------------------- /sanity_onlineshop/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Note: This config is only used to help editors like VS Code understand/resolve 3 | // parts, the actual transpilation is done by babel. Any compiler configuration in 4 | // here will be ignored. 5 | "include": ["./node_modules/@sanity/base/types/**/*.ts", "./**/*.ts", "./**/*.tsx"] 6 | } 7 | -------------------------------------------------------------------------------- /pages/favorite.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Favorites from "../components/favorite"; 3 | import Breadcrumb from "../components/UI/Breadcrumb"; 4 | 5 | const favorite = () => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default favorite; 15 | -------------------------------------------------------------------------------- /components/header/menu/SideBarMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SideBar from "./SideBar"; 3 | import SideNavSide from "./SideNavSide"; 4 | 5 | const index = () => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default index; 15 | -------------------------------------------------------------------------------- /components/header/menu/MegaMenu/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ExtraMenu from "./ExtraMenu"; 3 | import MegaMenu from "./MegaMenu"; 4 | 5 | const index = () => { 6 | return ( 7 |
8 | 9 | 10 |
11 | ); 12 | }; 13 | 14 | export default index; 15 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /components/UI/discountCountdown/ExpiredNotice.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLanguage } from "../../../hooks/useLanguage"; 3 | 4 | const ExpiredNotice = () => { 5 | const { t } = useLanguage(); 6 | return ( 7 |
8 |

{t.expireDiscount}

9 |
10 | ); 11 | }; 12 | 13 | export default ExpiredNotice; 14 | -------------------------------------------------------------------------------- /utilities/currencyFormat.ts: -------------------------------------------------------------------------------- 1 | // Rial currency format 2 | export const irrCurrencyFormat = (price: number | undefined) => { 3 | return price ? new Intl.NumberFormat("fa-IR").format(price) : null; 4 | }; 5 | 6 | //pound currency format 7 | export const gbpCurrencyFormat = (price: number | undefined) => { 8 | return price ? new Intl.NumberFormat("en-GB").format(price) : null; 9 | }; 10 | -------------------------------------------------------------------------------- /utilities/sortByCost.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "../lib/types/products"; 2 | 3 | export const sortByExpensive = ( 4 | product1: IProduct, 5 | product2: IProduct 6 | ): number => { 7 | return product2.price - product1.price; 8 | }; 9 | 10 | export const sortByCheapest = ( 11 | product1: IProduct, 12 | product2: IProduct 13 | ): number => { 14 | return product1.price - product2.price; 15 | }; 16 | -------------------------------------------------------------------------------- /lib/types/user.ts: -------------------------------------------------------------------------------- 1 | export interface IUser { 2 | _id?: any; 3 | name?: string; 4 | password?: string; 5 | email: string; 6 | isAdmin?: boolean; 7 | token?: string; 8 | } 9 | 10 | export interface IUserInfo { 11 | userInformation: IUser | null; 12 | } 13 | 14 | //RootState interface=> used for state type in useSelector hook 15 | 16 | export interface IUserInfoRootState { 17 | userInfo: IUserInfo; 18 | } 19 | -------------------------------------------------------------------------------- /components/UI/SectionTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLanguage } from "../../../hooks/useLanguage"; 3 | 4 | interface Props { 5 | title: string; 6 | } 7 | const SectionTitle: React.FC = ({ title }) => { 8 | const { t } = useLanguage(); 9 | return ( 10 |

{t[`${title}`]}

11 | ); 12 | }; 13 | 14 | export default SectionTitle; 15 | -------------------------------------------------------------------------------- /lib/types/pagePathsParams.ts: -------------------------------------------------------------------------------- 1 | export interface ICategoryPathsParams { 2 | category: string; 3 | } 4 | 5 | export interface ISubCategoryPathsParams { 6 | category: string; 7 | subCategory: string; 8 | } 9 | 10 | export interface ITitlePathsParams { 11 | category: string; 12 | subCategory: string; 13 | title: string; 14 | } 15 | 16 | export interface ISlugPathsParams { 17 | slug: { current: string }; 18 | category: string; 19 | subCategory: string; 20 | title: string; 21 | } 22 | -------------------------------------------------------------------------------- /lib/client.ts: -------------------------------------------------------------------------------- 1 | import sanityClient from "@sanity/client"; 2 | import imageUrlBuilder from "@sanity/image-url"; 3 | 4 | //connect to sanity 5 | export const client = sanityClient({ 6 | projectId: "3c4n15ly", 7 | dataset: "production", 8 | apiVersion: "2022-06-04", 9 | useCdn: true, 10 | token: process.env.NEXT_PUBLIC_SANITY_TOKEN, 11 | }); 12 | 13 | //be able to use sanity images 14 | const builder = imageUrlBuilder(client); 15 | 16 | export const urlFor = (source:any) => builder.image(source) -------------------------------------------------------------------------------- /components/header/user/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { IUserInfoRootState } from "../../../lib/types/user"; 4 | import UserAccountBtn from "./UserAccountBtn"; 5 | import LoginBtn from "./LoginBtn"; 6 | 7 | const User = () => { 8 | const userInfo = useSelector( 9 | (state: IUserInfoRootState) => state.userInfo.userInformation 10 | ); 11 | return
{userInfo ? : }
; 12 | }; 13 | 14 | export default User; 15 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { AppProps } from "next/app"; 2 | 3 | import Layout from "../components/layout/Layout"; 4 | 5 | import "slick-carousel/slick/slick.css"; 6 | import "slick-carousel/slick/slick-theme.css"; 7 | 8 | import "react-toastify/dist/ReactToastify.css"; 9 | 10 | import "../styles/globals.css"; 11 | 12 | function MyApp({ Component, pageProps }: AppProps) { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default MyApp; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo -------------------------------------------------------------------------------- /sanity_onlineshop/README.md: -------------------------------------------------------------------------------- 1 | # Sanity Clean Content Studio 2 | 3 | Congratulations, you have now installed the Sanity Content Studio, an open source real-time content editing environment connected to the Sanity backend. 4 | 5 | Now you can do the following things: 6 | 7 | - [Read “getting started” in the docs](https://www.sanity.io/docs/introduction/getting-started?utm_source=readme) 8 | - [Join the community Slack](https://slack.sanity.io/?utm_source=readme) 9 | - [Extend and build plugins](https://www.sanity.io/docs/content-studio/extending?utm_source=readme) 10 | -------------------------------------------------------------------------------- /components/brands/BrandBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | 4 | interface Props { 5 | brandName: string; 6 | imageSrc: string; 7 | } 8 | const BrandBox: React.FC = ({ brandName, imageSrc }) => { 9 | return ( 10 |
11 | {brandName} 12 |
13 |
14 | ); 15 | }; 16 | 17 | export default BrandBox; 18 | -------------------------------------------------------------------------------- /sanity_onlineshop/schemas/user.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'user', 3 | title: 'User', 4 | type: 'document', 5 | fields: [ 6 | { 7 | name: 'name', 8 | title: 'Name', 9 | type: 'string', 10 | }, 11 | 12 | { 13 | name: 'email', 14 | title: 'Email', 15 | type: 'string', 16 | }, 17 | { 18 | name: 'password', 19 | title: 'Password', 20 | type: 'string', 21 | }, 22 | { 23 | name: 'isAdmin', 24 | title: 'Is Admin', 25 | type: 'boolean', 26 | }, 27 | ], 28 | }; 29 | -------------------------------------------------------------------------------- /store/cartUI-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { ICartUI } from "../lib/types/cart"; 3 | 4 | const initialState: ICartUI = { 5 | cartBoxIsVisible: false, 6 | }; 7 | 8 | const cartUiSlice = createSlice({ 9 | name: "cartUi", 10 | initialState, 11 | reducers: { 12 | toggleCartBox(state, action: PayloadAction) { 13 | state.cartBoxIsVisible = action.payload; 14 | }, 15 | }, 16 | }); 17 | 18 | export const cartUiActions = cartUiSlice.actions; 19 | 20 | export default cartUiSlice.reducer; 21 | -------------------------------------------------------------------------------- /pages/cart.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import CartList from "../components/cart/CartList"; 3 | import Breadcrumb from "../components/UI/Breadcrumb"; 4 | import OrderSummaryBox from "../components/cart/OrderSummaryBox"; 5 | 6 | const cart: NextPage = () => { 7 | return ( 8 |
9 | 10 |
11 | 12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default cart; 19 | -------------------------------------------------------------------------------- /mock/banner.js: -------------------------------------------------------------------------------- 1 | export const bannerContent = [ 2 | { 3 | title: "larisaTitle", 4 | description: "larisaDescription", 5 | buttonText: "see", 6 | imgSrc: "/images/banners-img/home1.webp", 7 | imgWidth: 980, 8 | imgHeight: 500, 9 | numberOfDiscountDate: 9, 10 | href: "/", 11 | }, 12 | { 13 | title: "romanoTitle", 14 | description: "romanoDescription", 15 | buttonText: "see", 16 | imgSrc: "/images/banners-img/home2.webp", 17 | imgWidth: 980, 18 | imgHeight: 500, 19 | numberOfDiscountDate: 7, 20 | href: "/", 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /mock/benefits.js: -------------------------------------------------------------------------------- 1 | export const benefitContent = [ 2 | { 3 | imgSrc: "/images/benefit-icons/005-delivery-truck-2.webp", 4 | title: "deliver", 5 | }, 6 | { 7 | imgSrc: "/images/benefit-icons/003-cash-on-delivery.webp", 8 | title: "cash", 9 | }, 10 | { 11 | imgSrc: "/images/benefit-icons/004-headphones.webp", 12 | title: "support", 13 | }, 14 | { 15 | imgSrc: "/images/benefit-icons/006-best-seller.webp", 16 | title: "warrantyBenefit", 17 | }, 18 | // { 19 | // imgSrc: "/images/benefit-icons/007-return.png", 20 | // title: "return", 21 | // }, 22 | ]; 23 | -------------------------------------------------------------------------------- /sanity_onlineshop/config/.checksums: -------------------------------------------------------------------------------- 1 | { 2 | "#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!", 3 | "@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea", 4 | "@sanity/default-login": "e2ed4e51e97331c0699ba7cf9f67cbf76f1c6a5f806d6eabf8259b2bcb5f1002", 5 | "@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa", 6 | "@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6", 7 | "@sanity/vision": "da5b6ed712703ecd04bf4df560570c668aa95252c6bc1c41d6df1bda9b8b8f60" 8 | } 9 | -------------------------------------------------------------------------------- /sanity_onlineshop/sanity.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "project": { 4 | "name": "onlineshop" 5 | }, 6 | "api": { 7 | "projectId": "3c4n15ly", 8 | "dataset": "production" 9 | }, 10 | "plugins": [ 11 | "@sanity/base", 12 | "@sanity/default-layout", 13 | "@sanity/default-login", 14 | "@sanity/desk-tool" 15 | ], 16 | "env": { 17 | "development": { 18 | "plugins": [ 19 | "@sanity/vision" 20 | ] 21 | } 22 | }, 23 | "parts": [ 24 | { 25 | "name": "part:@sanity/base/schema", 26 | "path": "./schemas/schema" 27 | } 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /store/megaMenu-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { IMegaMenuShow } from "../lib/types/megaMenu"; 3 | 4 | const initialState: IMegaMenuShow = { 5 | isMegaMenuOpen: false, 6 | }; 7 | 8 | const megaMenuSlice = createSlice({ 9 | name: "megaMenu", 10 | initialState, 11 | reducers: { 12 | openMegaMenu(state) { 13 | state.isMegaMenuOpen = true; 14 | }, 15 | closeMegaMenu(state) { 16 | state.isMegaMenuOpen = false; 17 | }, 18 | }, 19 | }); 20 | 21 | export const megaMenuActions = megaMenuSlice.actions; 22 | 23 | export default megaMenuSlice.reducer; 24 | -------------------------------------------------------------------------------- /components/header/Logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import React from "react"; 4 | 5 | const Logo = () => { 6 | return ( 7 | 8 | 9 | zishop-logo 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default Logo; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "suppressImplicitAnyIndexErrors": true 18 | }, 19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /store/newestProduct-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | import { IProduct } from "../lib/types/products"; 4 | import { IProductList } from "../lib/types/productList"; 5 | 6 | const initialState: IProductList = { 7 | productsList: [], 8 | }; 9 | 10 | const newestProductsSlice = createSlice({ 11 | name: "newestProducts", 12 | initialState, 13 | reducers: { 14 | addProducts(state, action: PayloadAction) { 15 | state.productsList = action.payload; 16 | }, 17 | }, 18 | }); 19 | 20 | export const newestProductsActions = newestProductsSlice.actions; 21 | 22 | export default newestProductsSlice.reducer; 23 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | reactStrictMode: true, 5 | i18n: { 6 | locales: ["en", "fa"], 7 | defaultLocale: "en", 8 | localeDetection: false, 9 | }, 10 | images: { 11 | domains: ["cdn.sanity.io"], 12 | }, 13 | webpack: function (config) { 14 | config.module.rules.push({ 15 | test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/, 16 | use: { 17 | loader: "url-loader", 18 | options: { 19 | limit: 100000, 20 | name: "[name].[ext]", 21 | }, 22 | }, 23 | }); 24 | return config; 25 | }, 26 | }; 27 | 28 | module.exports = nextConfig; 29 | -------------------------------------------------------------------------------- /store/settingBox-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | import { ISettingBox } from "../lib/types/settingBox"; 3 | 4 | const initialState: ISettingBox = { 5 | isOpen: false, 6 | }; 7 | 8 | const settingBoxSlice = createSlice({ 9 | name: "settingBox", 10 | initialState, 11 | reducers: { 12 | openSettingBox(state) { 13 | state.isOpen = true; 14 | }, 15 | closeSettingBox(state) { 16 | state.isOpen = false; 17 | }, 18 | toggleSettingBox(state) { 19 | state.isOpen = !state.isOpen; 20 | }, 21 | }, 22 | }); 23 | 24 | export const settingBoxActions = settingBoxSlice.actions; 25 | 26 | export default settingBoxSlice.reducer; 27 | -------------------------------------------------------------------------------- /components/productDetails/SimilarProducts.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IProduct } from "../../lib/types/products"; 3 | import CarouselBox from "../UI/CarouselBox/CarouselBox"; 4 | import CarouselBoxCard from "../UI/CarouselBox/CarouselBoxCard"; 5 | 6 | interface Props { 7 | products: IProduct[]; 8 | } 9 | const SimilarProducts: React.FC = ({ products }) => { 10 | return ( 11 |
12 | 13 | {products.map((product) => ( 14 | 15 | ))} 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default SimilarProducts; 22 | -------------------------------------------------------------------------------- /store/specialOfferProducts-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | import { IProduct } from "../lib/types/products"; 4 | import { IOfferProducts } from "../lib/types/offerProductsState"; 5 | 6 | const initialState: IOfferProducts = { 7 | specialOfferProducts: [], 8 | }; 9 | 10 | const specialOfferProductsSlice = createSlice({ 11 | name: "specialOfferProducts", 12 | initialState, 13 | reducers: { 14 | addProducts(state, action: PayloadAction) { 15 | state.specialOfferProducts = action.payload; 16 | }, 17 | }, 18 | }); 19 | 20 | export const specialOfferProductsActions = specialOfferProductsSlice.actions; 21 | 22 | export default specialOfferProductsSlice.reducer; 23 | -------------------------------------------------------------------------------- /components/header/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { GoSearch } from "react-icons/go"; 4 | import { useLanguage } from "../../hooks/useLanguage"; 5 | 6 | //TODO: search by product name. 7 | const SearchBar = () => { 8 | const { t } = useLanguage(); 9 | return ( 10 |
11 | 12 | 17 |
18 | ); 19 | }; 20 | 21 | export default SearchBar; 22 | -------------------------------------------------------------------------------- /sanity_onlineshop/schemas/schema.js: -------------------------------------------------------------------------------- 1 | // First, we must import the schema creator 2 | import createSchema from "part:@sanity/base/schema-creator"; 3 | 4 | // Then import schema types from any plugins that might expose them 5 | import schemaTypes from "all:part:@sanity/base/schema-type"; 6 | 7 | import product from "./product"; 8 | import user from "./user"; 9 | 10 | // Then we give our schema to the builder and provide the result to Sanity 11 | export default createSchema({ 12 | // We name our schema 13 | name: "default", 14 | // Then proceed to concatenate our document type 15 | // to the ones provided by any plugins that are installed 16 | types: schemaTypes.concat([ 17 | /* Your types here! */ 18 | product, 19 | user, 20 | ]), 21 | }); 22 | -------------------------------------------------------------------------------- /components/cart/CartList.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { ICartRootState, ICartProduct } from "../../lib/types/cart"; 4 | import CartItem from "./CartItem"; 5 | 6 | const CartList = () => { 7 | const cartItems = useSelector((state: ICartRootState) => state.cart.items); 8 | return ( 9 |
10 |
11 | {cartItems.length 12 | ? cartItems.map((cartItem: ICartProduct) => { 13 | return ( 14 | 15 | ); 16 | }) 17 | : null} 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default CartList; 24 | -------------------------------------------------------------------------------- /store/user-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import Cookies from "js-cookie"; 3 | import { IUser, IUserInfo } from "../lib/types/user"; 4 | 5 | const initialState: IUserInfo = { 6 | userInformation: Cookies.get("userInfo") 7 | ? JSON.parse(Cookies.get("userInfo")!) 8 | : null, 9 | }; 10 | 11 | const userInfoSlice = createSlice({ 12 | name: "userInfo", 13 | initialState, 14 | reducers: { 15 | userLogin(state, action: PayloadAction) { 16 | state.userInformation = action.payload; 17 | }, 18 | userLogout(state) { 19 | state.userInformation = null; 20 | }, 21 | }, 22 | }); 23 | export const userInfoActions = userInfoSlice.actions; 24 | 25 | export default userInfoSlice.reducer; 26 | -------------------------------------------------------------------------------- /components/UI/discountCountdown/DiscountCountdown.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useCountdown } from "../../../hooks/useCountdown"; 3 | import ExpiredNotice from "./ExpiredNotice"; 4 | import ShowCounter from "./ShowCounter"; 5 | 6 | interface Props { 7 | targetDate: number; 8 | } 9 | const DiscountFlipCountdown: React.FC = ({ targetDate }) => { 10 | const [days, hours, minutes, seconds] = useCountdown(targetDate); 11 | if (days + hours + minutes + seconds <= 0) { 12 | return ; 13 | } else { 14 | return ( 15 | 21 | ); 22 | } 23 | }; 24 | 25 | export default DiscountFlipCountdown; 26 | -------------------------------------------------------------------------------- /pages/offers.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { GetStaticProps } from "next"; 3 | import { client } from "../lib/client"; 4 | import { IProduct } from "../lib/types/products"; 5 | import ProductList from "../components/productList/ProductList"; 6 | 7 | const offers: NextPage<{ 8 | products: IProduct[]; 9 | }> = ({ products }) => { 10 | return ( 11 |
12 | 13 |
14 | ); 15 | }; 16 | 17 | export default offers; 18 | 19 | export const getStaticProps: GetStaticProps = async () => { 20 | const productQuery = `*[_type=='product'&& isOffer==true]`; 21 | const products = await client.fetch(productQuery); 22 | return { 23 | props: { 24 | products: products, 25 | }, 26 | }; 27 | }; 28 | -------------------------------------------------------------------------------- /store/activeMenuItem-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { IActiveMenuItem } from "../lib/types/activeMenuItem"; 3 | 4 | const initialState: IActiveMenuItem = { 5 | activeMenuItemIndex: 0, 6 | activeMenuItemText: "", 7 | }; 8 | 9 | const activeMenuItemSlice = createSlice({ 10 | name: "activeMenuItem", 11 | initialState, 12 | reducers: { 13 | setActiveMenuItemIndex(state, action: PayloadAction) { 14 | state.activeMenuItemIndex = action.payload; 15 | }, 16 | setActiveMenuItemText(state, action: PayloadAction) { 17 | state.activeMenuItemText = action.payload; 18 | }, 19 | }, 20 | }); 21 | 22 | export const activeMenuItemActions = activeMenuItemSlice.actions; 23 | 24 | export default activeMenuItemSlice.reducer; 25 | -------------------------------------------------------------------------------- /utilities/sortByTimeStamp.ts: -------------------------------------------------------------------------------- 1 | import { IProduct } from "../lib/types/products"; 2 | 3 | export function getTimeStamp(date: string) { 4 | const creationProductDate = new Date(date); 5 | return creationProductDate.getTime(); 6 | } 7 | 8 | export const sortByTimeStamp = ( 9 | product1: IProduct, 10 | product2: IProduct 11 | ): number => { 12 | if (product2?.timeStamp && product1?.timeStamp) { 13 | return product2?.timeStamp - product1?.timeStamp; 14 | } 15 | return 0; 16 | }; 17 | 18 | export const newestProductsFn = (products: IProduct[]) => { 19 | const productsWithTimeStamp = products.map((product) => { 20 | return { 21 | ...product, 22 | timeStamp: getTimeStamp(product.registerDate!), 23 | }; 24 | }); 25 | return productsWithTimeStamp.sort(sortByTimeStamp); 26 | }; 27 | -------------------------------------------------------------------------------- /lib/types/cart.ts: -------------------------------------------------------------------------------- 1 | import { IProductDetails, TSlug } from "./products"; 2 | 3 | export interface ICartProduct { 4 | image: any; 5 | name: string; 6 | slug: TSlug; 7 | price: number; 8 | discount?: number; 9 | brand: string; 10 | category: string[]; 11 | starRating: number; 12 | isOffer?: boolean; 13 | details?: IProductDetails[]; 14 | registerDate?: string; 15 | quantity: number; 16 | totalPrice: number; 17 | } 18 | 19 | export interface ICartUI { 20 | cartBoxIsVisible: boolean; 21 | } 22 | 23 | export interface ICart { 24 | items: ICartProduct[]; 25 | totalQuantity: number; 26 | totalAmount: number; 27 | } 28 | 29 | //RootState interface => use for state type in useSelector hook 30 | 31 | export interface ICartUiRootState { 32 | cartUi: ICartUI; 33 | } 34 | export interface ICartRootState { 35 | cart: ICart; 36 | } 37 | -------------------------------------------------------------------------------- /components/Offers/Offers.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CarouselBox from "../UI/CarouselBox/CarouselBox"; 3 | import { useSelector } from "react-redux"; 4 | import { IProduct } from "../../lib/types/products"; 5 | import CarouselBoxCard from "../UI/CarouselBox/CarouselBoxCard"; 6 | 7 | const Offers = () => { 8 | const OfferProducts = useSelector( 9 | (state: any) => state.specialOfferProductsList.specialOfferProducts 10 | ); 11 | 12 | return ( 13 |
14 | 15 | {OfferProducts.slice(0, 10).map((product: IProduct) => { 16 | return ; 17 | })} 18 | 19 |
20 | ); 21 | }; 22 | 23 | export default Offers; 24 | -------------------------------------------------------------------------------- /components/header/menu/MegaMenu/ExtraMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { extraMenu } from "../../../../mock/menuItems"; 4 | import { useLanguage } from "../../../../hooks/useLanguage"; 5 | 6 | const ExtraMenu = () => { 7 | const { t } = useLanguage(); 8 | return ( 9 |
10 | {extraMenu.map((menuItem) => { 11 | return ( 12 |
16 | 17 | 18 | {t[`${menuItem.title}`]} 19 | 20 |
21 | ); 22 | })} 23 |
24 | ); 25 | }; 26 | 27 | export default ExtraMenu; 28 | -------------------------------------------------------------------------------- /components/UI/discountCountdown/DateTimeDisplay.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLanguage } from "../../../hooks/useLanguage"; 3 | 4 | interface Props { 5 | value: number; 6 | type: string; 7 | isDanger: boolean; 8 | } 9 | const DateTimeDisplay: React.FC = ({ value, type, isDanger }) => { 10 | const { t, locale } = useLanguage(); 11 | const dateTime = 12 | locale === "en" 13 | ? new Intl.NumberFormat("en-EN").format(value) 14 | : new Intl.NumberFormat("fa-IR").format(value); 15 | return ( 16 |
21 |

{dateTime}

22 | {t[`${type}`]} 23 |
24 | ); 25 | }; 26 | 27 | export default DateTimeDisplay; 28 | -------------------------------------------------------------------------------- /hooks/useWindowDimensions.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | 3 | export function useWindowDimensions() { 4 | let width, height; 5 | if (typeof window !== "undefined") { 6 | width = window.innerWidth; 7 | height = window.innerHeight; 8 | } else { 9 | width = 0; 10 | height = 0; 11 | } 12 | 13 | const [windowDimensions, setWindowDimensions] = useState({ 14 | width, 15 | height, 16 | }); 17 | 18 | useEffect(() => { 19 | function getWindowDimensions() { 20 | const { innerWidth: width, innerHeight: height } = window; 21 | return { 22 | width, 23 | height, 24 | }; 25 | } 26 | function handleResize() { 27 | setWindowDimensions(getWindowDimensions()); 28 | } 29 | 30 | window.addEventListener("resize", handleResize); 31 | return () => window.removeEventListener("resize", handleResize); 32 | }, []); 33 | 34 | return windowDimensions; 35 | } 36 | -------------------------------------------------------------------------------- /store/favorite-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { IFavorite } from "../lib/types/favorite"; 3 | import { IProduct } from "../lib/types/products"; 4 | 5 | const initialState: IFavorite = { 6 | items: [], 7 | }; 8 | 9 | const favoriteSlice = createSlice({ 10 | name: "favorite", 11 | initialState, 12 | reducers: { 13 | addToFavorite(state, action: PayloadAction) { 14 | state.items.push({ 15 | ...action.payload, 16 | }); 17 | }, 18 | removeFromFavorite(state, action: PayloadAction) { 19 | const productSlug = action.payload; 20 | state.items = state.items.filter( 21 | (item) => item.slug.current !== productSlug 22 | ); 23 | }, 24 | clearCart(state) { 25 | state = initialState; 26 | }, 27 | }, 28 | }); 29 | 30 | export const favoriteActions = favoriteSlice.actions; 31 | 32 | export default favoriteSlice.reducer; 33 | -------------------------------------------------------------------------------- /components/header/user/LoginBtn.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLanguage } from "../../../hooks/useLanguage"; 3 | import { HiOutlineLogin } from "react-icons/hi"; 4 | import Link from "next/link"; 5 | 6 | const LoginBtn = () => { 7 | const { t } = useLanguage(); 8 | return ( 9 | 10 | 11 |
12 | 13 |

14 | {t.login} | {t.signUp} 15 |

16 |
17 |
18 | 19 |
20 |
21 | 22 | ); 23 | }; 24 | 25 | export default LoginBtn; 26 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from "next/document"; 2 | export default function Document() { 3 | return ( 4 | 5 | 6 | 10 | 11 | 16 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /components/favorite/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | import { IFavoriteRootState } from "../../lib/types/favorite"; 5 | import FavoriteItem from "./FavoriteItem"; 6 | 7 | const Favorites = () => { 8 | const { t } = useLanguage(); 9 | const favoriteItems = useSelector( 10 | (state: IFavoriteRootState) => state.favorite.items 11 | ); 12 | return ( 13 |
14 | {favoriteItems.length ? ( 15 |
16 | {favoriteItems.map((favoriteItem) => ( 17 | 21 | ))} 22 |
23 | ) : ( 24 |

{t.thereAreNoFavorites}

25 | )} 26 |
27 | ); 28 | }; 29 | 30 | export default Favorites; 31 | -------------------------------------------------------------------------------- /sanity_onlineshop/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "onlineshop", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "", 6 | "main": "package.json", 7 | "author": "zahra mirzaei ", 8 | "license": "UNLICENSED", 9 | "scripts": { 10 | "start": "sanity start", 11 | "build": "sanity build" 12 | }, 13 | "keywords": [ 14 | "sanity" 15 | ], 16 | "dependencies": { 17 | "@sanity/base": "^2.30.0", 18 | "@sanity/core": "^2.30.0", 19 | "@sanity/default-layout": "^2.30.0", 20 | "@sanity/default-login": "^2.30.0", 21 | "@sanity/desk-tool": "^2.30.0", 22 | "@sanity/eslint-config-studio": "^2.0.0", 23 | "@sanity/vision": "^2.30.0", 24 | "eslint": "^8.6.0", 25 | "prop-types": "^15.7", 26 | "react": "^17.0", 27 | "react-dom": "^17.0", 28 | "styled-components": "^5.2.0" 29 | }, 30 | "devDependencies": {}, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/ZahraMirzaei/online-shop.git" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pages/api/users/login.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import nc from "next-connect"; 3 | import bcrypt from "bcryptjs"; 4 | import { client } from "../../../lib/client"; 5 | import { signToken } from "../../../utilities/auth"; 6 | 7 | const handler = nc(); 8 | 9 | handler.post(async (req: NextApiRequest, res: NextApiResponse) => { 10 | const user = await client.fetch(`*[_type == "user" && email == $email][0]`, { 11 | email: req.body.email, 12 | }); 13 | if (user && bcrypt.compareSync(req.body.password, user.password)) { 14 | const token = signToken({ 15 | _id: user._id, 16 | name: user.name, 17 | email: user.email, 18 | isAdmin: user.isAdmin, 19 | }); 20 | res.send({ 21 | _id: user._id, 22 | name: user.name, 23 | email: user.email, 24 | isAdmin: user.isAdmin, 25 | token, 26 | }); 27 | } else { 28 | res.status(401).send({ message: "Invalid_email_or_password" }); 29 | } 30 | }); 31 | 32 | export default handler; 33 | -------------------------------------------------------------------------------- /components/UI/discountCountdown/ShowCounter.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DateTimeDisplay from "./DateTimeDisplay"; 3 | 4 | interface Props { 5 | days: number; 6 | hours: number; 7 | minutes: number; 8 | seconds: number; 9 | } 10 | const ShowCounter: React.FC = ({ days, hours, minutes, seconds }) => { 11 | return ( 12 |
13 | 14 |

:

15 | 16 |

:

17 | 18 |

:

19 | 20 |
21 | ); 22 | }; 23 | 24 | export default ShowCounter; 25 | -------------------------------------------------------------------------------- /components/UI/CarouselBox/CarouselBoxArrows.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface Props { 4 | className?: string; 5 | style?: any; 6 | onClick?: () => void; 7 | } 8 | export const NextArrow: React.FC = ({ className, style, onClick }) => { 9 | return ( 10 |
15 | ); 16 | }; 17 | export const PrevArrow: React.FC = ({ className, style, onClick }) => { 18 | return ( 19 |
24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /store/sideNavBar-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { IDropDown } from "../lib/types/dropDown"; 3 | import { ISideNavBar } from "../lib/types/sidebar"; 4 | 5 | const initialState: ISideNavBar = { 6 | isSidebarOpen: false, 7 | isNavbarOpen: false, 8 | dropDownList: [], 9 | }; 10 | 11 | const sideNavBarSlice = createSlice({ 12 | name: "sideNavBar", 13 | initialState, 14 | reducers: { 15 | openSidebar(state) { 16 | state.isSidebarOpen = true; 17 | }, 18 | 19 | openNavbar(state) { 20 | state.isNavbarOpen = true; 21 | }, 22 | 23 | closeSidebar(state) { 24 | state.isSidebarOpen = false; 25 | }, 26 | 27 | closeNavbar(state) { 28 | state.isSidebarOpen = false; 29 | state.isNavbarOpen = false; 30 | }, 31 | 32 | setSidebarEntries(state, action: PayloadAction) { 33 | state.dropDownList = action.payload; 34 | }, 35 | }, 36 | }); 37 | 38 | export const sideNavBarActions = sideNavBarSlice.actions; 39 | 40 | export default sideNavBarSlice.reducer; 41 | -------------------------------------------------------------------------------- /components/Benefits/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | import { benefitContent } from "../../mock/benefits"; 5 | 6 | const Benefits = () => { 7 | const { t } = useLanguage(); 8 | return ( 9 |
10 | {benefitContent.map((benefitItem) => { 11 | return ( 12 |
16 | {benefitItem.title} 23 |

24 | {t[`${benefitItem.title}`]} 25 |

26 |
27 | ); 28 | })} 29 |
30 | ); 31 | }; 32 | 33 | export default Benefits; 34 | -------------------------------------------------------------------------------- /mock/slider.js: -------------------------------------------------------------------------------- 1 | export const sliderContent = [ 2 | { 3 | ID: 1, 4 | title: "digitalBT", 5 | description: "digitalBD", 6 | bgImg: "url('/images/slider-img/digital-banner.webp')", 7 | url: "/", 8 | }, 9 | { 10 | ID: 2, 11 | title: "stationeryBT", 12 | description: "stationeryBD", 13 | bgImg: "url('/images/slider-img/stationery-banner.webp')", 14 | url: "/", 15 | }, 16 | { 17 | ID: 3, 18 | title: "toyBT", 19 | description: "toyBD", 20 | bgImg: "url('/images/slider-img/toy-banner.webp')", 21 | url: "/", 22 | }, 23 | { 24 | ID: 4, 25 | title: "houseBT", 26 | description: "houseBD", 27 | bgImg: "url('/images/slider-img/house-banner.webp')", 28 | url: "/", 29 | }, 30 | { 31 | ID: 5, 32 | title: "fashionBT", 33 | description: "fashionBD", 34 | bgImg: "url('/images/slider-img/fashion-banner.webp')", 35 | url: "/", 36 | }, 37 | { 38 | ID: 6, 39 | title: "beautyBT", 40 | description: "beautyBD", 41 | bgImg: "url('/images/slider-img/beauty-banner.webp')", 42 | url: "/", 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /components/carousel/Arrows.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface Props { 4 | className?: string; 5 | style?: any; 6 | onClick?: () => void; 7 | to: string; 8 | } 9 | export const NextArrow: React.FC = ({ 10 | className, 11 | style, 12 | onClick, 13 | to, 14 | }) => { 15 | return ( 16 |
22 | ); 23 | }; 24 | export const PrevArrow: React.FC = ({ 25 | className, 26 | style, 27 | onClick, 28 | to, 29 | }) => { 30 | return ( 31 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /mock/category-sm.js: -------------------------------------------------------------------------------- 1 | export const categorySmContent = [ 2 | { 3 | bgc: "digitalCategory", 4 | imgSrc: "digital-category.webp", 5 | categoryTitle: "digitalCategoryTitle", 6 | href: "/digital", 7 | }, 8 | { 9 | bgc: "fashionCategory", 10 | imgSrc: "fashion-category.webp", 11 | categoryTitle: "fashionCategoryTitle", 12 | href: "/fashion", 13 | }, 14 | { 15 | bgc: "beautyCategory", 16 | imgSrc: "beauty-category.webp", 17 | categoryTitle: "beautyCategoryTitle", 18 | href: "/beauty", 19 | }, 20 | { 21 | bgc: "sportCategory", 22 | imgSrc: "sport-category.webp", 23 | categoryTitle: "sportCategoryTitle", 24 | href: "/sport", 25 | }, 26 | { 27 | bgc: "houseCategory", 28 | imgSrc: "house-category.webp", 29 | categoryTitle: "houseCategoryTitle", 30 | href: "/house", 31 | }, 32 | { 33 | bgc: "toyCategory", 34 | imgSrc: "toy-category.webp", 35 | categoryTitle: "toyCategoryTitle", 36 | href: "/toy", 37 | }, 38 | { 39 | bgc: "stationeryCategory", 40 | imgSrc: "stationery-category.webp", 41 | categoryTitle: "stationeryCategoryTitle", 42 | href: "/stationery", 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /lib/types/products.ts: -------------------------------------------------------------------------------- 1 | export interface IProductDetails { 2 | processor?: string; 3 | screen?: string; 4 | operating_system?: string; 5 | ram?: string; 6 | ssd?: string; 7 | ports?: string; 8 | graphic?: string; 9 | warranty?: string; 10 | back_camera?: string; 11 | front_camera?: string; 12 | battery?: string; 13 | frequency_response?: string; 14 | microphone?: boolean; 15 | wireless?: boolean; 16 | wireless_standby_time?: boolean; 17 | connectionType?: string[]; 18 | connectors?: string[]; 19 | bluetooth?: boolean; 20 | noise_cancelling?: boolean; 21 | sound_isolating?: boolean; 22 | } 23 | 24 | export type TSlug = { 25 | _type: string; 26 | current: string; 27 | }; 28 | 29 | export type TImage = { 30 | _key: string; 31 | _type: "image"; 32 | asset: { 33 | _ref: string; 34 | _type: "reference"; 35 | }; 36 | }; 37 | 38 | export interface IProduct { 39 | image: any; 40 | name: string; 41 | slug: TSlug; 42 | price: number; 43 | discount?: number; 44 | details?: IProductDetails[]; 45 | brand: string; 46 | category: string[]; 47 | isOffer?: boolean; 48 | registerDate?: string; 49 | timeStamp?: number; 50 | starRating: number; 51 | } 52 | -------------------------------------------------------------------------------- /pages/newestProducts.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { useState, useEffect } from "react"; 3 | import { GetStaticProps } from "next"; 4 | import { client } from "../lib/client"; 5 | import { IProduct } from "../lib/types/products"; 6 | import ProductList from "../components/productList/ProductList"; 7 | import { newestProductsFn } from "../utilities/sortByTimeStamp"; 8 | 9 | const NewestProduct: NextPage<{ 10 | products: IProduct[]; 11 | }> = ({ products }) => { 12 | const [productsList, setProductsList] = useState([]); 13 | 14 | useEffect(() => { 15 | setProductsList(newestProductsFn(products)); 16 | }, [products]); 17 | 18 | return ( 19 |
20 | {productsList.length ? : null} 21 |
22 | ); 23 | }; 24 | 25 | export default NewestProduct; 26 | 27 | export const getStaticProps: GetStaticProps = async () => { 28 | const productQuery = `*[_type=='product' && slug.current != "asus-zenbook-14-intel-core-i7-16gb-ram-512gb-ssd-14-ips-laptop"]`; 29 | const products = await client.fetch(productQuery); 30 | 31 | return { 32 | props: { 33 | products: products, 34 | }, 35 | }; 36 | }; 37 | -------------------------------------------------------------------------------- /components/footer/footerContent/FooterColumns.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | import { footerContent } from "../../../mock/footer"; 4 | import { useLanguage } from "../../../hooks/useLanguage"; 5 | 6 | const FooterColumns = () => { 7 | const { t } = useLanguage(); 8 | return ( 9 |
10 | {footerContent.map((item) => { 11 | return ( 12 |
13 |

14 | {t[item.title]} 15 |

16 |
17 | {item.subtitles.map((subItem) => { 18 | return ( 19 | 20 | 21 | {t[subItem.text]} 22 | 23 | 24 | ); 25 | })} 26 |
27 |
28 | ); 29 | })} 30 |
31 | ); 32 | }; 33 | 34 | export default FooterColumns; 35 | -------------------------------------------------------------------------------- /components/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider } from "react-redux"; 3 | import Head from "next/head"; 4 | import { ThemeProvider } from "next-themes"; 5 | import Header from "../header"; 6 | import store from "../../store/index"; 7 | import Footer from "../footer"; 8 | import { ToastContainer } from "react-toastify"; 9 | import { useLanguage } from "../../hooks/useLanguage"; 10 | import NextNProgress from "nextjs-progressbar"; 11 | 12 | const Layout: React.FC<{ children?: React.ReactNode }> = ({ children }) => { 13 | const { locale } = useLanguage(); 14 | return ( 15 | 16 | 17 | 18 | ZiShop 19 | 20 |
21 | 22 |
23 |
{children}
24 |
25 |
26 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default Layout; 38 | -------------------------------------------------------------------------------- /hooks/useCountdown.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | Date.prototype.addDays = function (days: number): Date { 4 | var date = new Date(this.valueOf()); 5 | date.setDate(date.getDate() + days); 6 | return date; 7 | }; 8 | 9 | var date = new Date(); 10 | 11 | const getReturnValues = (countDown: number) => { 12 | // calculate time left 13 | const days = Math.floor(countDown / (1000 * 60 * 60 * 24)); 14 | const hours = Math.floor( 15 | (countDown % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60) 16 | ); 17 | const minutes = Math.floor((countDown % (1000 * 60 * 60)) / (1000 * 60)); 18 | const seconds = Math.floor((countDown % (1000 * 60)) / 1000); 19 | 20 | return [days, hours, minutes, seconds]; 21 | }; 22 | 23 | const useCountdown = (targetDate: number) => { 24 | var expireDate = date.addDays(targetDate); 25 | const countDownDate = new Date(expireDate).getTime(); 26 | 27 | const [countDown, setCountDown] = useState( 28 | countDownDate - new Date().getTime() 29 | ); 30 | 31 | useEffect(() => { 32 | const interval = setInterval(() => { 33 | setCountDown(countDownDate - new Date().getTime()); 34 | }, 1000); 35 | 36 | return () => clearInterval(interval); 37 | }, [countDownDate]); 38 | 39 | return getReturnValues(countDown); 40 | }; 41 | 42 | export { useCountdown }; 43 | -------------------------------------------------------------------------------- /components/banners/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BannerBox from "./banner-box/BannerBox"; 3 | import { bannerContent } from "../../mock/banner"; 4 | import SectionTitle from "../UI/SectionTitle"; 5 | 6 | const Banner = () => { 7 | return ( 8 |
9 | 10 |
11 | {bannerContent.map( 12 | ({ 13 | title, 14 | description, 15 | numberOfDiscountDate, 16 | href, 17 | imgHeight, 18 | imgSrc, 19 | imgWidth, 20 | buttonText, 21 | }) => { 22 | return ( 23 | 34 | ); 35 | } 36 | )} 37 |
38 |
39 | ); 40 | }; 41 | 42 | export default Banner; 43 | -------------------------------------------------------------------------------- /components/header/theme/Theme.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useTheme } from "next-themes"; 3 | import { MdOutlineLightMode } from "react-icons/md"; 4 | import { BiMoon } from "react-icons/bi"; 5 | import { useLanguage } from "../../../hooks/useLanguage"; 6 | import ThemeItem from "./ThemeItem"; 7 | const Theme = () => { 8 | const { t } = useLanguage(); 9 | 10 | const { systemTheme, theme } = useTheme(); 11 | const currentTheme = theme === "system" ? systemTheme : theme; 12 | 13 | const renderThemeChanger = () => { 14 | if (currentTheme === "dark") { 15 | return ; 16 | } else { 17 | return ; 18 | } 19 | }; 20 | 21 | return ( 22 |
23 |
24 |

{t.theme}

25 |
26 | 31 | 32 |
33 |
34 |
{renderThemeChanger()}
35 |
36 | ); 37 | }; 38 | 39 | export default Theme; 40 | -------------------------------------------------------------------------------- /components/productDetails/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IProduct } from "../../lib/types/products"; 3 | import Breadcrumb from "../UI/Breadcrumb"; 4 | import ImageSection from "./ImageSection"; 5 | import DetailsSection from "./DetailsSection"; 6 | import Benefits from "../Benefits"; 7 | import SimilarProducts from "./SimilarProducts"; 8 | 9 | interface Props { 10 | product: IProduct; 11 | products: IProduct[]; 12 | } 13 | const ProductDetails: React.FC = ({ product, products }) => { 14 | const similarProductsList = products 15 | .filter( 16 | (similarProduct) => similarProduct.slug.current !== product.slug.current 17 | ) 18 | .slice(0, 10); 19 | 20 | return ( 21 |
22 | 23 |
24 |
25 | 26 | 27 |
28 |
29 | 30 |
31 | 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default ProductDetails; 38 | -------------------------------------------------------------------------------- /store/index.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | 3 | import specialOfferProductsReducer from "./specialOfferProducts-slice"; 4 | import newestProductReducer from "./newestProduct-slice"; 5 | import SortedProductsListReducer from "./sortedProductList-slice"; 6 | import cartUiReducer from "./cartUI-slice"; 7 | import cartSliceReducer from "./cart-slice"; 8 | import userInfoReducer from "./user-slice"; 9 | import sideNavBarReducer from "./sideNavBar-slice"; 10 | import megaMenuReducer from "./megaMenu-slice"; 11 | import activeMenuItemReducer from "./activeMenuItem-slice"; 12 | import settingBoxReducer from "./settingBox-slice"; 13 | import favoriteReducer from "./favorite-slice"; 14 | 15 | const store = configureStore({ 16 | reducer: { 17 | specialOfferProductsList: specialOfferProductsReducer, 18 | newestProductsList: newestProductReducer, 19 | sortedProductsList: SortedProductsListReducer, 20 | cartUi: cartUiReducer, 21 | cart: cartSliceReducer, 22 | userInfo: userInfoReducer, 23 | sideNavBar: sideNavBarReducer, 24 | megaMenu: megaMenuReducer, 25 | activeMenuItem: activeMenuItemReducer, 26 | settingBox: settingBoxReducer, 27 | favorite: favoriteReducer, 28 | }, 29 | middleware: (getDefaultMiddleware) => 30 | getDefaultMiddleware({ 31 | serializableCheck: false, 32 | }), 33 | }); 34 | 35 | export default store; 36 | -------------------------------------------------------------------------------- /components/carousel/Slide.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | 5 | interface Props { 6 | ID?: number; 7 | title: string; 8 | description: string; 9 | bgImg: string; 10 | url: string; 11 | } 12 | const Slide: React.FC = ({ title, description, bgImg, url }) => { 13 | const { t } = useLanguage(); 14 | 15 | return ( 16 | <> 17 | 36 | 37 | ); 38 | }; 39 | 40 | export default Slide; 41 | -------------------------------------------------------------------------------- /pages/[category]/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { GetStaticProps, GetStaticPaths } from "next"; 3 | import { client } from "../../lib/client"; 4 | import { IProduct } from "../../lib/types/products"; 5 | import ProductList from "../../components/productList/ProductList"; 6 | import { ICategoryPathsParams } from "../../lib/types/pagePathsParams"; 7 | 8 | const categoryPage: NextPage<{ 9 | products: IProduct[]; 10 | }> = ({ products }) => { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default categoryPage; 19 | 20 | export const getStaticPaths: GetStaticPaths = async () => { 21 | const query = `*[_type=="product"]{ 22 | "category":category[0] 23 | }`; 24 | const products = await client.fetch(query); 25 | const paths = products.map((product: ICategoryPathsParams) => ({ 26 | params: { 27 | category: product.category, 28 | }, 29 | })); 30 | return { 31 | fallback: "blocking", 32 | paths, 33 | }; 34 | }; 35 | 36 | export const getStaticProps: GetStaticProps = async (context) => { 37 | const category = context.params?.category; 38 | const productQuery = `*[_type=='product'&& category[0]=="${category}"]`; 39 | const products = await client.fetch(productQuery); 40 | 41 | return { 42 | props: { 43 | products: products, 44 | }, 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /components/category/CategorySmBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import { useLanguage } from "../../hooks/useLanguage"; 5 | 6 | interface Props { 7 | imgSrc: string; 8 | bgc: string; 9 | categoryTitle: string; 10 | href: string; 11 | } 12 | const CategorySmBox: React.FC = ({ 13 | imgSrc, 14 | bgc, 15 | categoryTitle, 16 | href, 17 | }) => { 18 | const { t, locale } = useLanguage(); 19 | return ( 20 | 21 | 22 |
29 |
32 | {categoryTitle} 39 |
40 |

41 | {t[`${categoryTitle}`]} 42 |

43 |
44 |
45 | 46 | ); 47 | }; 48 | 49 | export default CategorySmBox; 50 | -------------------------------------------------------------------------------- /components/brands/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import BrandBox from "./BrandBox"; 3 | import { brandContent } from "../../mock/brand"; 4 | import Slider from "react-slick"; 5 | import SectionTitle from "../UI/SectionTitle"; 6 | const Brands = () => { 7 | const settings = { 8 | infinite: true, 9 | speed: 6000, 10 | slidesToShow: 8, 11 | slidesToScroll: 4, 12 | autoplay: true, 13 | autoplaySpeed: 8000, 14 | cssEase: "linear", 15 | swipeToSlide: true, 16 | responsive: [ 17 | { 18 | breakpoint: 1024, 19 | settings: { 20 | slidesToShow: 6, 21 | slidesToScroll: 3, 22 | }, 23 | }, 24 | { 25 | breakpoint: 768, 26 | settings: { 27 | slidesToShow: 4, 28 | slidesToScroll: 4, 29 | }, 30 | }, 31 | { 32 | breakpoint: 640, 33 | settings: { 34 | slidesToShow: 3, 35 | slidesToScroll: 3, 36 | }, 37 | }, 38 | ], 39 | }; 40 | return ( 41 |
42 | 43 | 44 | {brandContent.map((brandItem) => { 45 | return ( 46 | 51 | ); 52 | })} 53 | 54 |
55 | ); 56 | }; 57 | 58 | export default Brands; 59 | -------------------------------------------------------------------------------- /components/header/Settings.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { AiOutlineSetting } from "react-icons/ai"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { settingBoxActions } from "../../store/settingBox-slice"; 5 | import Theme from "./theme/Theme"; 6 | import Language from "./language/Language"; 7 | import { ISettingBoxRootState } from "../../lib/types/settingBox"; 8 | 9 | const Settings = () => { 10 | const dispatch = useDispatch(); 11 | 12 | const isSettingBoxOpen = useSelector( 13 | (state: ISettingBoxRootState) => state.settingBox.isOpen 14 | ); 15 | 16 | function toggleShowSettingBox() { 17 | dispatch(settingBoxActions.toggleSettingBox()); 18 | } 19 | 20 | function onCloseSettingBox() { 21 | dispatch(settingBoxActions.closeSettingBox()); 22 | } 23 | 24 | return ( 25 |
26 |
27 | 28 |
29 | {isSettingBoxOpen ? ( 30 | <> 31 |
35 |
36 | 37 |
38 | 39 |
40 | 41 | ) : null} 42 |
43 | ); 44 | }; 45 | 46 | export default Settings; 47 | -------------------------------------------------------------------------------- /components/header/language/LanguageItem.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { useRouter } from "next/router"; 3 | import React, { useState } from "react"; 4 | import { useLanguage } from "../../../hooks/useLanguage"; 5 | 6 | interface Props { 7 | language: string; 8 | onCloseBox: (isLangOpen: boolean) => void; 9 | } 10 | const LanguageItem: React.FC = ({ language, onCloseBox }) => { 11 | const { t, locale } = useLanguage(); 12 | const router = useRouter(); 13 | const [lang, setLang] = useState(locale); 14 | 15 | function onChangeHandler(e: React.ChangeEvent) { 16 | setLang(e.currentTarget.id); 17 | } 18 | 19 | return ( 20 | 21 | 22 |
onCloseBox(false)}> 23 | 32 | 40 |
41 |
42 | 43 | ); 44 | }; 45 | 46 | export default LanguageItem; 47 | -------------------------------------------------------------------------------- /components/carousel/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Slider from "react-slick"; 3 | import Slide from "./Slide"; 4 | import { sliderContent } from "../../mock/slider"; 5 | import { NextArrow, PrevArrow } from "./Arrows"; 6 | import { HiOutlineChevronRight, HiOutlineChevronLeft } from "react-icons/hi"; 7 | 8 | const Carousel = () => { 9 | const settings = { 10 | dots: true, 11 | infinite: true, 12 | speed: 500, 13 | slidesToShow: 1, 14 | slidesToScroll: 1, 15 | autoplay: true, 16 | autoplaySpeed: 5000, 17 | cssEase: "linear", 18 | nextArrow: , 19 | prevArrow: , 20 | appendDots: (dots: string) => ( 21 |
22 |
    {dots}
23 |
24 | ), 25 | }; 26 | 27 | return ( 28 |
29 | 30 | {sliderContent.map((slideContent) => { 31 | return ; 32 | })} 33 | 34 | <> 35 |
36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 | ); 44 | }; 45 | 46 | export default Carousel; 47 | -------------------------------------------------------------------------------- /components/header/menu/MegaMenu/MenusContainer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { activeMenuItemActions } from "../../../../store/activeMenuItem-slice"; 4 | import menuItems from "../../../../mock/menuItems"; 5 | import MenuItems from "../../../UI/MenuItems/MenuItems"; 6 | import { IDropDown } from "../../../../lib/types/dropDown"; 7 | import SubMenu from "./SubMenu"; 8 | const MenusContainer = () => { 9 | const [subMenuItems, setSubMenuItems] = useState(); 10 | const dispatch = useDispatch(); 11 | function activeItem( 12 | submenuList: IDropDown[] | undefined, 13 | activeItemIndex: number, 14 | activeItemName: string 15 | ) { 16 | setSubMenuItems(submenuList); 17 | dispatch(activeMenuItemActions.setActiveMenuItemIndex(activeItemIndex)); 18 | dispatch(activeMenuItemActions.setActiveMenuItemText(activeItemName)); 19 | } 20 | 21 | useEffect(() => { 22 | setSubMenuItems(menuItems[0].productsGroup); 23 | return () => { 24 | dispatch(activeMenuItemActions.setActiveMenuItemIndex(0)); 25 | dispatch(activeMenuItemActions.setActiveMenuItemText("digital")); 26 | }; 27 | // eslint-disable-next-line react-hooks/exhaustive-deps 28 | }, []); 29 | 30 | return ( 31 |
32 | 35 | 36 |
37 | ); 38 | }; 39 | 40 | export default MenusContainer; 41 | -------------------------------------------------------------------------------- /components/newest/Newest.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | import { useWindowDimensions } from "../../hooks/useWindowDimensions"; 5 | import Link from "next/link"; 6 | import Card from "../UI/card/Card"; 7 | import { IProduct } from "../../lib/types/products"; 8 | import SectionTitle from "../UI/SectionTitle"; 9 | 10 | const Newest = () => { 11 | const { t } = useLanguage(); 12 | const { width } = useWindowDimensions(); 13 | let numProductToShow = width >= 1536 ? 12 : 8; 14 | 15 | const newestProducts: IProduct[] = useSelector( 16 | (state: any) => state.newestProductsList.productsList 17 | ); 18 | 19 | return ( 20 |
21 | 22 | 23 |
24 | {newestProducts 25 | ? newestProducts 26 | .slice(0, numProductToShow) 27 | .map((product: IProduct) => { 28 | return ; 29 | }) 30 | : null} 31 |
32 | 33 | 40 |
41 | ); 42 | }; 43 | 44 | export default Newest; 45 | -------------------------------------------------------------------------------- /pages/[category]/[subCategory]/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { GetStaticProps, GetStaticPaths } from "next"; 3 | import { client } from "../../../lib/client"; 4 | import { IProduct } from "../../../lib/types/products"; 5 | import ProductList from "../../../components/productList/ProductList"; 6 | import { ISubCategoryPathsParams } from "../../../lib/types/pagePathsParams"; 7 | 8 | const subCategory: NextPage<{ 9 | products: IProduct[]; 10 | }> = ({ products }) => { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default subCategory; 19 | 20 | export const getStaticPaths: GetStaticPaths = async () => { 21 | const query = `*[_type=="product"]{ 22 | "category":category[0], 23 | "subCategory":category[1], 24 | }`; 25 | const products = await client.fetch(query); 26 | const paths = products.map((product: ISubCategoryPathsParams) => ({ 27 | params: { 28 | category: product.category.toString(), 29 | subCategory: product.subCategory.toString(), 30 | }, 31 | })); 32 | return { 33 | fallback: "blocking", 34 | paths, 35 | }; 36 | }; 37 | 38 | export const getStaticProps: GetStaticProps = async (context) => { 39 | const subCategory = context.params?.subCategory; 40 | const category = context.params?.category; 41 | const productQuery = `*[_type=='product'&& category[0]=="${category}" && category[1]=="${subCategory}"]`; 42 | const products = await client.fetch(productQuery); 43 | 44 | return { 45 | props: { 46 | products: products, 47 | }, 48 | }; 49 | }; 50 | -------------------------------------------------------------------------------- /components/header/theme/ThemeItem.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "next-themes"; 2 | import React from "react"; 3 | import { IconType } from "react-icons"; 4 | import { useLanguage } from "../../../hooks/useLanguage"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { settingBoxActions } from "../../../store/settingBox-slice"; 7 | import { ISettingBoxRootState } from "../../../lib/types/settingBox"; 8 | 9 | interface Props { 10 | theme: string; 11 | currentTheme?: string; 12 | Icon: IconType; 13 | } 14 | const ThemeItem: React.FC = ({ theme, Icon, currentTheme }) => { 15 | const { t } = useLanguage(); 16 | const { setTheme } = useTheme(); 17 | const dispatch = useDispatch(); 18 | const isSettingBoxOpen = useSelector( 19 | (state: ISettingBoxRootState) => state.settingBox.isOpen 20 | ); 21 | 22 | function onThemeClickHandler() { 23 | setTheme(theme); 24 | isSettingBoxOpen && dispatch(settingBoxActions.closeSettingBox()); 25 | } 26 | 27 | return ( 28 |
34 | 42 |

{t[`${theme}`]}

43 |
44 | ); 45 | }; 46 | 47 | export default ThemeItem; 48 | -------------------------------------------------------------------------------- /pages/login.tsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import type { NextPage } from "next"; 3 | import { useDispatch } from "react-redux"; 4 | import { useRouter } from "next/router"; 5 | import jsCookie from "js-cookie"; 6 | import EnteringBox from "../components/entering/EnteringBox"; 7 | import { IUser, IUserInfoRootState } from "../lib/types/user"; 8 | import { userInfoActions } from "../store/user-slice"; 9 | import { getError } from "../utilities/error"; 10 | import { useEffect, useState } from "react"; 11 | import { useSelector } from "react-redux"; 12 | const Login: NextPage = () => { 13 | const dispatch = useDispatch(); 14 | const router = useRouter(); 15 | const [errorMessage, setErrorMessage] = useState(""); 16 | const userInfo = useSelector((state: IUserInfoRootState) => { 17 | return state.userInfo.userInformation; 18 | }); 19 | useEffect(() => { 20 | if (userInfo) { 21 | router.push("/"); 22 | } 23 | }, [userInfo, router]); 24 | async function LoginHandler(user: IUser) { 25 | const { email, password } = user; 26 | try { 27 | const { data } = await axios.post("/api/users/login", { 28 | email, 29 | password, 30 | }); 31 | dispatch(userInfoActions.userLogin(data)); 32 | jsCookie.set("userInfo", JSON.stringify(data)); 33 | router.push("/"); 34 | } catch (err: any) { 35 | setErrorMessage(getError(err)); 36 | console.log(getError(err)); 37 | } 38 | } 39 | return ( 40 | 45 | ); 46 | }; 47 | 48 | export default Login; 49 | -------------------------------------------------------------------------------- /pages/[category]/[subCategory]/[title]/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { GetStaticProps, GetStaticPaths } from "next"; 3 | import { client } from "../../../../lib/client"; 4 | import { IProduct } from "../../../../lib/types/products"; 5 | import ProductList from "../../../../components/productList/ProductList"; 6 | import { ITitlePathsParams } from "../../../../lib/types/pagePathsParams"; 7 | 8 | const brandPage: NextPage<{ 9 | products: IProduct[]; 10 | }> = ({ products }) => { 11 | return ( 12 |
13 | 14 |
15 | ); 16 | }; 17 | 18 | export default brandPage; 19 | 20 | export const getStaticPaths: GetStaticPaths = async () => { 21 | const query = `*[_type=="product"]{ 22 | "category":category[0], 23 | "subCategory":category[1], 24 | "title":category[2], 25 | }`; 26 | const products = await client.fetch(query); 27 | const paths = products.map((product: ITitlePathsParams) => ({ 28 | params: { 29 | category: product.category, 30 | subCategory: product.subCategory, 31 | title: product.title, 32 | }, 33 | })); 34 | return { 35 | fallback: "blocking", 36 | paths, 37 | }; 38 | }; 39 | 40 | export const getStaticProps: GetStaticProps = async (context) => { 41 | const title = context.params?.title; 42 | const subCategory = context.params?.subCategory; 43 | const productQuery = `*[_type=='product'&& category[1]=="${subCategory}" && category[2]=="${title}"]`; 44 | 45 | const products = await client.fetch(productQuery); 46 | 47 | return { 48 | props: { 49 | products: products, 50 | }, 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /store/sortedProductList-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | 3 | import { IProductList } from "../lib/types/productList"; 4 | import { IProduct } from "../lib/types/products"; 5 | import { sortByPoPularity } from "../utilities/sortByPopularity"; 6 | import { sortByCheapest, sortByExpensive } from "../utilities/sortByCost"; 7 | import { newestProductsFn } from "../utilities/sortByTimeStamp"; 8 | 9 | const initialState: IProductList = { 10 | productsList: [], 11 | }; 12 | 13 | const SortedProductsListSlice = createSlice({ 14 | name: "sortedProductsList", 15 | initialState, 16 | reducers: { 17 | sortProductsList( 18 | state, 19 | action: PayloadAction<{ productsList: IProduct[]; sortBasedOn: string }> 20 | ) { 21 | switch (action.payload.sortBasedOn) { 22 | case "all": 23 | state.productsList = action.payload.productsList; 24 | break; 25 | case "newestProducts": { 26 | state.productsList = newestProductsFn(state.productsList); 27 | break; 28 | } 29 | case "popular": { 30 | state.productsList = state.productsList.sort(sortByPoPularity); 31 | break; 32 | } 33 | case "cheapest": { 34 | state.productsList = state.productsList.sort(sortByCheapest); 35 | break; 36 | } 37 | case "expensive": { 38 | state.productsList = state.productsList.sort(sortByExpensive); 39 | break; 40 | } 41 | } 42 | }, 43 | }, 44 | }); 45 | export const SortedProductsListActions = SortedProductsListSlice.actions; 46 | 47 | export default SortedProductsListSlice.reducer; 48 | -------------------------------------------------------------------------------- /components/header/menu/SideBarMenu/SideNav.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from "react"; 2 | import { useRouter } from "next/router"; 3 | import { IoClose } from "react-icons/io5"; 4 | import SideNavContent from "./SideNavContent"; 5 | import Logo from "../../Logo"; 6 | 7 | interface Props { 8 | state?: string; 9 | onClose: () => void; 10 | children?: React.ReactNode; 11 | ref: React.HTMLProps; 12 | } 13 | 14 | const SideNav = forwardRef(({ state, onClose }, ref) => { 15 | const { locale } = useRouter(); 16 | return ( 17 |
34 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 | ); 47 | }); 48 | 49 | SideNav.displayName = "SideNav"; 50 | 51 | export default SideNav; 52 | -------------------------------------------------------------------------------- /hooks/useExchangeRateGBPToIRR.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { irrCurrencyFormat } from "../utilities/currencyFormat"; 3 | 4 | const API_KEY = "230967b967bfa5f86ca99d6c"; 5 | const URL = `https://v6.exchangerate-api.com/v6/${API_KEY}/pair/GBP/IRR`; 6 | 7 | export const useExchangeRateGBPToIRR = (price: number) => { 8 | const [exchangePrice, setExchangePrice] = useState(0); 9 | const hardRateExchange = 52230.27; // each Pound into Rial 10 | useEffect(() => { 11 | const fetchExchangeRate = async () => { 12 | try { 13 | const result = await fetch(`${URL}/${price}`); 14 | if (result.ok) { 15 | const data = await result.json(); 16 | const irToman = Math.floor(data.conversion_result / 1000) * 1000; //common currency in Iran 17 | setExchangePrice(irToman); 18 | } else { 19 | const priceWithHardRateExchange = price * hardRateExchange; 20 | const irToman = Math.floor(priceWithHardRateExchange / 1000) * 1000; 21 | setExchangePrice(irToman); 22 | throw new Error(); 23 | } 24 | } catch (e) { 25 | console.log( 26 | `⚠ Error: the server responded with a status of 429 ()=> free account has reached the number of requests allowed by free plan and price exchange is based on hard number` 27 | ); 28 | } 29 | }; 30 | if (price > 0) { 31 | // fetchExchangeRate(); 32 | const priceWithHardRateExchange = price * hardRateExchange; 33 | const irToman = Math.ceil(priceWithHardRateExchange / 1000) * 1000; 34 | setExchangePrice(irToman); 35 | } 36 | }, [price]); 37 | 38 | return irrCurrencyFormat(exchangePrice); 39 | }; 40 | -------------------------------------------------------------------------------- /components/header/user/UserAccountBtn.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { AiOutlineUser } from "react-icons/ai"; 3 | import { HiChevronDown } from "react-icons/hi"; 4 | import { Transition } from "react-transition-group"; 5 | import UserAccountBox from "./UserAccountBox"; 6 | 7 | const UserAccountBtn = () => { 8 | const [isUserBoxOpen, setIsUserBoxOpen] = useState(false); 9 | const nodeRef = useRef(null); 10 | 11 | function onClose() { 12 | setIsUserBoxOpen((prev) => prev && false); 13 | } 14 | 15 | function onIconClickHandler() { 16 | setIsUserBoxOpen((prev) => !prev); 17 | } 18 | 19 | return ( 20 |
21 |
25 | 26 | 27 |
28 | 35 | {(state) => { 36 | return ( 37 | <> 38 |
39 |
43 | 44 |
45 | 46 | ); 47 | }} 48 |
49 |
50 | ); 51 | }; 52 | 53 | export default UserAccountBtn; 54 | -------------------------------------------------------------------------------- /components/header/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import dynamic from "next/dynamic"; 3 | import Menu from "./menu"; 4 | import Logo from "./Logo"; 5 | import Settings from "./Settings"; 6 | import SearchBar from "./SearchBar"; 7 | import CartIcon from "../cart/CartIcon"; 8 | import Language from "./language/Language"; 9 | 10 | const UserBox = dynamic(() => import("./user"), { 11 | ssr: false, 12 | }); 13 | const Theme = dynamic(() => import("./theme/Theme"), { 14 | ssr: false, 15 | }); 16 | 17 | const index = () => { 18 | return ( 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 | {/* 👈settings: md:hidden */} 27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 | 43 |
44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default index; 51 | -------------------------------------------------------------------------------- /mock/footer.js: -------------------------------------------------------------------------------- 1 | import { FaLinkedin, FaTwitterSquare, FaTelegramPlane } from "react-icons/fa"; 2 | import { AiFillInstagram } from "react-icons/ai"; 3 | 4 | export const footerContent = [ 5 | { 6 | title: "zishopMap", 7 | subtitles: [ 8 | { 9 | text: "aboutUs", 10 | href: "/about", 11 | }, 12 | { 13 | text: "contactUs", 14 | href: "/blank", 15 | }, 16 | { 17 | text: "saleInZishop", 18 | href: "/blank", 19 | }, 20 | { 21 | text: "careerOpportunities", 22 | href: "/blank", 23 | }, 24 | ], 25 | }, 26 | { 27 | title: "customerServices", 28 | subtitles: [ 29 | { 30 | text: "commonQuestions", 31 | href: "/blank", 32 | }, 33 | { 34 | text: "returnProcedures", 35 | href: "/blank", 36 | }, 37 | { 38 | text: "privacy", 39 | href: "/blank", 40 | }, 41 | ], 42 | }, 43 | { 44 | title: "shoppingGuide", 45 | subtitles: [ 46 | { 47 | text: "howToPlaceAnOrder", 48 | href: "/blank", 49 | }, 50 | { 51 | text: "orderSubmissionProcedure", 52 | href: "/blank", 53 | }, 54 | { 55 | text: "paymentMethods", 56 | href: "/blank", 57 | }, 58 | ], 59 | }, 60 | ]; 61 | 62 | export const socialMedia = [ 63 | { 64 | name: "instagram", 65 | icon: AiFillInstagram, 66 | href: "/", 67 | }, 68 | { 69 | name: "linkedin", 70 | icon: FaLinkedin, 71 | href: "/", 72 | }, 73 | { 74 | name: "twitter", 75 | icon: FaTwitterSquare, 76 | href: "/", 77 | }, 78 | { 79 | name: "telegram", 80 | icon: FaTelegramPlane, 81 | href: "/", 82 | }, 83 | ]; 84 | -------------------------------------------------------------------------------- /pages/api/users/register.ts: -------------------------------------------------------------------------------- 1 | import { NextApiRequest, NextApiResponse } from "next"; 2 | import nc from "next-connect"; 3 | import bcrypt from "bcryptjs"; 4 | import axios from "axios"; 5 | import config from "../../../lib/config"; 6 | import { client } from "../../../lib/client"; 7 | import { signToken } from "../../../utilities/auth"; 8 | 9 | const handler = nc(); 10 | 11 | handler.post(async (req: NextApiRequest, res: NextApiResponse) => { 12 | const projectId = config.projectId; 13 | const dataset = config.dataset; 14 | const tokenWithWriteAccess = process.env.NEXT_PUBLIC_SANITY_TOKEN; 15 | const createMutations = [ 16 | { 17 | create: { 18 | _type: "user", 19 | name: req.body.name, 20 | email: req.body.email, 21 | password: bcrypt.hashSync(req.body.password), 22 | isAdmin: false, 23 | }, 24 | }, 25 | ]; 26 | const existUser = await client.fetch( 27 | `*[_type == "user" && email == $email][0]`, 28 | { 29 | email: req.body.email, 30 | } 31 | ); 32 | if (existUser) { 33 | return res.status(401).send({ message: "Email_already_exists" }); 34 | } 35 | const { data } = await axios.post( 36 | `https://${projectId}.api.sanity.io/v2021-06-07/data/mutate/${dataset}?returnIds=true`, 37 | { mutations: createMutations }, 38 | { 39 | headers: { 40 | "Content-type": "application/json", 41 | Authorization: `Bearer ${tokenWithWriteAccess}`, 42 | }, 43 | } 44 | ); 45 | const userId = data.results[0].id; 46 | const user = { 47 | _id: userId, 48 | name: req.body.name, 49 | email: req.body.email, 50 | isAdmin: false, 51 | }; 52 | const token = signToken(user); 53 | res.send({ ...user, token }); 54 | }); 55 | 56 | export default handler; 57 | -------------------------------------------------------------------------------- /components/productDetails/ProductPageActions.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { favoriteActions } from "../../store/favorite-slice"; 4 | import { IProduct } from "../../lib/types/products"; 5 | import { IFavoriteRootState } from "../../lib/types/favorite"; 6 | import { RiHeartFill, RiHeartAddLine, RiShareLine } from "react-icons/ri"; 7 | 8 | interface Props { 9 | product: IProduct; 10 | } 11 | const ProductPageActions: React.FC = ({ product }) => { 12 | const dispatch = useDispatch(); 13 | const favoriteItems = useSelector( 14 | (state: IFavoriteRootState) => state.favorite.items 15 | ); 16 | const isInFavorite = favoriteItems.some( 17 | (item) => item.slug.current === product.slug.current 18 | ); 19 | let FavoriteIcon = isInFavorite ? RiHeartFill : RiHeartAddLine; 20 | function toggleFavoriteHandler() { 21 | !isInFavorite 22 | ? dispatch(favoriteActions.addToFavorite(product)) 23 | : dispatch(favoriteActions.removeFromFavorite(product.slug.current)); 24 | } 25 | return ( 26 |
27 |
31 | 37 |
38 |
39 | 40 |
41 |
42 | ); 43 | }; 44 | 45 | export default ProductPageActions; 46 | -------------------------------------------------------------------------------- /components/category/CategoryLgBox.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | import React from "react"; 4 | import { useLanguage } from "../../hooks/useLanguage"; 5 | 6 | interface Props { 7 | name: string; 8 | title: string; 9 | description: string; 10 | styles: { 11 | backgroundColor: string; 12 | flexDirection: string; 13 | paddingInline: string; 14 | paddingBlock: string; 15 | textAlign?: string; 16 | gridRow: string; 17 | gridColumn: string; 18 | }; 19 | href: string; 20 | imgSrc: string; 21 | imgWidth: number; 22 | imgHeight: number; 23 | } 24 | const CategoryLgBox: React.FC = ({ 25 | name, 26 | title, 27 | description, 28 | styles, 29 | href, 30 | imgSrc, 31 | imgWidth, 32 | imgHeight, 33 | }) => { 34 | const { t } = useLanguage(); 35 | 36 | return ( 37 |
42 |
43 |

{t[`${title}`]}

44 |

{t[`${description}`]}

45 | 46 | 47 | {t.seeAllProducts} 48 | 49 | 50 |
51 | {name} 58 |
59 | ); 60 | }; 61 | 62 | export default CategoryLgBox; 63 | -------------------------------------------------------------------------------- /components/UI/CarouselBox/CarouselBoxCard.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import { urlFor } from "../../../lib/client"; 4 | import { IProduct } from "../../../lib/types/products"; 5 | import Link from "next/link"; 6 | import ProductPrice from "../ProductPrice"; 7 | 8 | interface Props { 9 | product: IProduct; 10 | } 11 | 12 | const CarouselBoxCard: React.FC = ({ product }) => { 13 | return ( 14 | 49 | ); 50 | }; 51 | 52 | export default CarouselBoxCard; 53 | -------------------------------------------------------------------------------- /components/header/menu/SideBarMenu/SideBar.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { sideNavBarActions } from "../../../../store/sideNavBar-slice"; 4 | import { Transition } from "react-transition-group"; 5 | import { GoGrabber } from "react-icons/go"; 6 | import { ISideNavBarRootState } from "../../../../lib/types/sidebar"; 7 | import SideNav from "./SideNav"; 8 | 9 | const SideBar = () => { 10 | const nodeRef = useRef(null); 11 | const dispatch = useDispatch(); 12 | const isNavbarOpen = useSelector( 13 | (state: ISideNavBarRootState) => state.sideNavBar.isNavbarOpen 14 | ); 15 | const closeNav = () => { 16 | dispatch(sideNavBarActions.closeNavbar()); 17 | }; 18 | 19 | const openNavBar = () => { 20 | dispatch(sideNavBarActions.openNavbar()); 21 | }; 22 | 23 | return ( 24 |
25 |
26 | 27 |
28 | 35 | {(state) => { 36 | return ( 37 | <> 38 | 39 |
51 | 52 | ); 53 | }} 54 |
55 |
56 | ); 57 | }; 58 | 59 | export default SideBar; 60 | -------------------------------------------------------------------------------- /components/productList/SubmenuCategory.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useRouter } from "next/router"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | import menuItems from "../../mock/menuItems"; 5 | import { useWindowDimensions } from "../../hooks/useWindowDimensions"; 6 | 7 | const SubmenuCategory = () => { 8 | const { t } = useLanguage(); 9 | const router = useRouter(); 10 | const { width } = useWindowDimensions(); 11 | const iconFontSize = width <= 768 ? "1.5rem" : "2.5rem"; 12 | const category = router.query.category; 13 | const selectedCategory = menuItems.filter( 14 | (item) => item.category === category 15 | ); 16 | const subCategories = selectedCategory[0]?.productsGroup?.map((item) => ({ 17 | title: item.title, 18 | icon: item.icon, 19 | })); 20 | function onClickHandler(subCategory: string) { 21 | if (selectedCategory[0].category) { 22 | router.push(`/${selectedCategory[0].category}/${subCategory}`); 23 | } 24 | } 25 | 26 | return subCategories ? ( 27 |
28 |

{t.categories}

29 |
30 | {subCategories?.map((subCategory) => ( 31 |
onClickHandler(subCategory.title)} 35 | > 36 | 37 |

38 | {t[subCategory.title]} 39 |

40 |
41 | ))} 42 |
43 |
44 | ) : null; 45 | }; 46 | 47 | export default SubmenuCategory; 48 | -------------------------------------------------------------------------------- /components/header/user/UserAccountBox.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | import { useDispatch } from "react-redux"; 4 | import { userInfoActions } from "../../../store/user-slice"; 5 | import { useLanguage } from "../../../hooks/useLanguage"; 6 | import { AiOutlineHeart } from "react-icons/ai"; 7 | import { IoLogOutOutline } from "react-icons/io5"; 8 | import jsCookie from "js-cookie"; 9 | 10 | interface Props { 11 | onClose: () => void; 12 | } 13 | const UserAccountBox: React.FC = ({ onClose }) => { 14 | const { t } = useLanguage(); 15 | const dispatch = useDispatch(); 16 | function onLogoutClickHandler() { 17 | dispatch(userInfoActions.userLogout()); 18 | jsCookie.remove("userInfo"); 19 | onClose(); 20 | } 21 | return ( 22 |
23 | 53 |
54 | ); 55 | }; 56 | 57 | export default UserAccountBox; 58 | -------------------------------------------------------------------------------- /pages/about.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { useLanguage } from "../hooks/useLanguage"; 3 | import Image from "next/image"; 4 | import { RiDoubleQuotesL, RiDoubleQuotesR } from "react-icons/ri"; 5 | 6 | const About: NextPage = () => { 7 | const { t, locale } = useLanguage(); 8 | const StartQuot = locale === "en" ? RiDoubleQuotesL : RiDoubleQuotesR; 9 | const EndQuot = locale === "en" ? RiDoubleQuotesR : RiDoubleQuotesL; 10 | return ( 11 |
12 |
13 |

{t.aboutLongText}

14 |
15 |

16 | 24 | {t.cafeDX} 25 | 33 |   34 | 40 | CafeDX 41 | 42 |

43 |

{t.aboutEnjoy}

44 |

{t.myName}

45 |
46 |
47 | about me 54 |
55 |
56 | ); 57 | }; 58 | 59 | export default About; 60 | -------------------------------------------------------------------------------- /pages/[category]/[subCategory]/[title]/[slug]/index.tsx: -------------------------------------------------------------------------------- 1 | import type { GetStaticPaths, GetStaticProps, NextPage } from "next"; 2 | import React from "react"; 3 | import ProductDetails from "../../../../../components/productDetails"; 4 | import { client } from "../../../../../lib/client"; 5 | import { ISlugPathsParams } from "../../../../../lib/types/pagePathsParams"; 6 | import { IProduct } from "../../../../../lib/types/products"; 7 | 8 | const productDetailsPage: NextPage<{ 9 | product: IProduct; 10 | products: IProduct[]; 11 | }> = ({ product, products }) => { 12 | return ( 13 |
14 | 15 |
16 | ); 17 | }; 18 | 19 | export default productDetailsPage; 20 | 21 | export const getStaticPaths: GetStaticPaths = async () => { 22 | const query = `*[_type=="product"]{ 23 | slug{current}, 24 | "category":category[0], 25 | "subCategory":category[1], 26 | "title":category[2], 27 | }`; 28 | const products = await client.fetch(query); 29 | const paths = products.map((product: ISlugPathsParams) => ({ 30 | params: { 31 | slug: product.slug.current, 32 | category: product.category, 33 | subCategory: product.subCategory, 34 | title: product.title, 35 | }, 36 | })); 37 | return { 38 | fallback: "blocking", 39 | paths, 40 | }; 41 | }; 42 | 43 | export const getStaticProps: GetStaticProps = async (context) => { 44 | const slug = context.params?.slug; 45 | const category = context.params?.category; 46 | const subCategory = context.params?.subCategory; 47 | const productQuery = `*[_type=='product' && slug.current=="${slug}"][0]`; 48 | const productsQuery = `*[_type=='product' && category[0]=="${category}" && category[1]=="${subCategory}"]`; 49 | 50 | const product = await client.fetch(productQuery); 51 | const products = await client.fetch(productsQuery); 52 | 53 | return { 54 | props: { 55 | product, 56 | products, 57 | }, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /components/header/menu/SideBarMenu/SideNavContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useLanguage } from "../../../../hooks/useLanguage"; 4 | import { extraMenu } from "../../../../mock/menuItems"; 5 | import { useDispatch } from "react-redux"; 6 | import { sideNavBarActions } from "../../../../store/sideNavBar-slice"; 7 | import { activeMenuItemActions } from "../../../../store/activeMenuItem-slice"; 8 | import { IDropDown } from "../../../../lib/types/dropDown"; 9 | import MenuItems from "../../../UI/MenuItems/MenuItems"; 10 | 11 | const SideNavContent = () => { 12 | const { t } = useLanguage(); 13 | const dispatch = useDispatch(); 14 | const openNav = ( 15 | sidebarSideContent: IDropDown[] = [], 16 | activeItemName: string, 17 | activeItemIndex: number 18 | ) => { 19 | dispatch(sideNavBarActions.setSidebarEntries(sidebarSideContent)); 20 | dispatch(sideNavBarActions.openSidebar()); 21 | dispatch(activeMenuItemActions.setActiveMenuItemText(activeItemName)); 22 | dispatch(activeMenuItemActions.setActiveMenuItemIndex(activeItemIndex)); 23 | }; 24 | return ( 25 |
26 |
27 | {extraMenu.map((menuItem) => { 28 | return ( 29 |
33 | 34 | 35 | {t[`${menuItem.title}`]} 36 | 37 |
38 | ); 39 | })} 40 |
41 |
42 |

43 | {t.CategoryOfGoods} 44 |

45 | 46 |
47 | ); 48 | }; 49 | 50 | export default SideNavContent; 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "online-shop", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint", 10 | "postbuild": "next-sitemap" 11 | }, 12 | "dependencies": { 13 | "@fullhuman/postcss-purgecss": "^4.1.3", 14 | "@reduxjs/toolkit": "^1.8.2", 15 | "@sanity/client": "^3.3.0", 16 | "@sanity/image-url": "^1.0.1", 17 | "@stripe/stripe-js": "^1.29.0", 18 | "@types/bcryptjs": "^2.4.2", 19 | "@types/js-cookie": "^3.0.2", 20 | "@types/jsonwebtoken": "^8.5.8", 21 | "@types/react-redux": "^7.1.24", 22 | "@types/react-slick": "^0.23.8", 23 | "@types/react-star-rating-component": "^1.4.1", 24 | "@types/react-transition-group": "^4.4.4", 25 | "@zeit/next-css": "^1.0.2-canary.0", 26 | "axios": "^0.27.2", 27 | "bcryptjs": "^2.4.3", 28 | "js-cookie": "^3.0.1", 29 | "jsonwebtoken": "^8.5.1", 30 | "next": "12.1.6", 31 | "next-connect": "^0.12.2", 32 | "next-sanity-image": "^3.2.1", 33 | "next-sitemap": "^3.1.17", 34 | "next-themes": "^0.2.0", 35 | "nextjs-progressbar": "^0.0.14", 36 | "react": "18.1.0", 37 | "react-dom": "18.1.0", 38 | "react-hot-toast": "^2.2.0", 39 | "react-icons": "^4.3.1", 40 | "react-query": "^3.38.1", 41 | "react-redux": "^8.0.2", 42 | "react-slick": "^0.29.0", 43 | "react-star-rating-component": "^1.4.1", 44 | "react-toastify": "^9.0.7", 45 | "react-transition-group": "^4.4.2", 46 | "slick-carousel": "^1.8.1", 47 | "stripe": "^9.1.0", 48 | "url-loader": "^4.1.1" 49 | }, 50 | "devDependencies": { 51 | "@types/node": "17.0.31", 52 | "@types/react": "18.0.8", 53 | "@types/react-dom": "18.0.3", 54 | "autoprefixer": "^10.4.7", 55 | "eslint": "8.14.0", 56 | "eslint-config-next": "12.1.6", 57 | "postcss": "^8.4.14", 58 | "postcss-import": "^14.1.0", 59 | "tailwindcss": "^3.1.4", 60 | "typescript": "4.6.4" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /components/category/Category.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { categorySmContent } from "../../mock/category-sm"; 3 | import CategorySmBox from "./CategorySmBox"; 4 | import { categoryLgContent } from "../../mock/category-lg"; 5 | import CategoryLgBox from "./CategoryLgBox"; 6 | import SectionTitle from "../UI/SectionTitle"; 7 | 8 | const Category = () => { 9 | return ( 10 |
11 | 12 | 13 | {/* 📱 sm and md break point */} 14 |
15 | {categorySmContent.map((categoryItem) => { 16 | return ( 17 | 24 | ); 25 | })} 26 |
27 | 28 | {/* 💻lg break point */} 29 |
30 | {categoryLgContent.map( 31 | ({ 32 | name, 33 | title, 34 | description, 35 | styles, 36 | href, 37 | imgSrc, 38 | imgWidth, 39 | imgHeight, 40 | }) => { 41 | return ( 42 | 53 | ); 54 | } 55 | )} 56 |
57 |
58 | ); 59 | }; 60 | 61 | export default Category; 62 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { useEffect } from "react"; 3 | import dynamic from "next/dynamic"; 4 | 5 | import { useDispatch } from "react-redux"; 6 | import { specialOfferProductsActions } from "../store/specialOfferProducts-slice"; 7 | import { newestProductsActions } from "../store/newestProduct-slice"; 8 | 9 | import { client } from "../lib/client"; 10 | 11 | import Benefits from "../components/Benefits"; 12 | import Carousel from "../components/carousel"; 13 | const Offers = dynamic(() => import("../components/Offers/Offers")); 14 | const Category = dynamic(() => import("../components/category/Category")); 15 | const Newest = dynamic(() => import("../components/newest/Newest")); 16 | const Brands = dynamic(() => import("../components/brands")); 17 | const Banners = dynamic(() => import("../components/banners"), { ssr: false }); 18 | 19 | import { IProduct } from "../lib/types/products"; 20 | import { newestProductsFn } from "../utilities/sortByTimeStamp"; 21 | 22 | const Home: NextPage<{ products: IProduct[] }> = ({ products }) => { 23 | const dispatch = useDispatch(); 24 | 25 | useEffect(() => { 26 | //add products to offers list 27 | const offersProducts = products.filter((item) => item.discount); 28 | dispatch(specialOfferProductsActions.addProducts(offersProducts)); 29 | 30 | //add products to newest list 31 | const sortedProductsByTimeStamp = newestProductsFn(products); 32 | dispatch(newestProductsActions.addProducts(sortedProductsByTimeStamp)); 33 | }, [dispatch, products]); 34 | 35 | return ( 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
45 | ); 46 | }; 47 | 48 | export default Home; 49 | 50 | export const getStaticProps = async () => { 51 | const productQuery = `*[_type=='product']`; 52 | const products = await client.fetch(productQuery); 53 | 54 | return { 55 | props: { 56 | products, 57 | }, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /pages/signUp.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from "next"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { userInfoActions } from "../store/user-slice"; 4 | import jsCookie from "js-cookie"; 5 | import EnteringBox from "../components/entering/EnteringBox"; 6 | import { IUser } from "../lib/types/user"; 7 | import axios from "axios"; 8 | import { getError } from "../utilities/error"; 9 | import { useRouter } from "next/router"; 10 | import { useEffect, useState } from "react"; 11 | import { IUserInfoRootState } from "../lib/types/user"; 12 | const SignUp: NextPage = () => { 13 | const dispatch = useDispatch(); 14 | const router = useRouter(); 15 | const [errorMessage, setErrorMessage] = useState(""); 16 | const { redirect } = router.query; 17 | const userInfo = useSelector( 18 | (state: IUserInfoRootState) => state.userInfo.userInformation 19 | ); 20 | useEffect(() => { 21 | if (userInfo) { 22 | router.push((redirect as string) || "/"); 23 | } 24 | }, [userInfo, redirect, router]); 25 | async function signUpHandler(user: IUser) { 26 | const { name, email, password } = user; 27 | try { 28 | const { data } = await axios.post("/api/users/register", { 29 | name, 30 | email, 31 | password, 32 | }); 33 | dispatch(userInfoActions.userLogin(data)); 34 | jsCookie.set("userInfo", JSON.stringify(data)); 35 | router.push("/"); 36 | } catch (err: any) { 37 | /* sanity.io is boycott for the people from Iran so I set cookies for whom don't use VPN in Iran*/ 38 | if (err.response.data.status == 500) { 39 | dispatch(userInfoActions.userLogin(user)); 40 | jsCookie.set("userInfo", JSON.stringify(user)); 41 | } 42 | setErrorMessage(getError(err)); 43 | console.log(getError(err)); 44 | // router.push("/"); 45 | } 46 | } 47 | return ( 48 | 53 | ); 54 | }; 55 | 56 | export default SignUp; 57 | -------------------------------------------------------------------------------- /components/productList/Sort.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent } from "react"; 2 | import { useLanguage } from "../../hooks/useLanguage"; 3 | import { BsFilterLeft, BsArrowDown } from "react-icons/bs"; 4 | import { radioBtnValue } from "../../mock/sortRadioInput"; 5 | 6 | interface Props { 7 | selectedBtn: string; 8 | onChangeSelectedBtn: (e: ChangeEvent) => void; 9 | } 10 | const Sort: React.FC = ({ 11 | selectedBtn: selectedRadioBtn, 12 | onChangeSelectedBtn, 13 | }) => { 14 | const { t } = useLanguage(); 15 | 16 | const isRadioSelected = (value: string): boolean => 17 | value === selectedRadioBtn ? true : false; 18 | 19 | return ( 20 |
21 |
22 |
23 | 24 | 25 |
26 |
{t.sort}
27 |
28 | 29 |
30 | {radioBtnValue.map((radioInput) => { 31 | return ( 32 |
33 | 43 | 52 |
53 | ); 54 | })} 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default Sort; 61 | -------------------------------------------------------------------------------- /components/productDetails/ImageSection.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React, { useState } from "react"; 3 | import { urlFor } from "../../lib/client"; 4 | import { IProduct, TImage } from "../../lib/types/products"; 5 | import ProductPageActions from "./ProductPageActions"; 6 | 7 | interface Props { 8 | imgArray: TImage[]; 9 | product: IProduct; 10 | } 11 | const ImageSection: React.FC = ({ imgArray, product }) => { 12 | const [selectedImg, setSelectedImg] = useState(0); 13 | function onClickHandler(index: number) { 14 | setSelectedImg(index); 15 | } 16 | return ( 17 |
18 | 19 |
20 |
21 | product img 28 |
29 | 30 |
31 | {imgArray.map((imgItem: TImage, index: number) => { 32 | return ( 33 |
onClickHandler(index)} 41 | > 42 | product img 49 |
50 | ); 51 | })} 52 |
53 |
54 |
55 | ); 56 | }; 57 | 58 | export default ImageSection; 59 | -------------------------------------------------------------------------------- /components/footer/footerContent/SocialPart.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { socialMedia } from "../../../mock/footer"; 3 | import { useLanguage } from "../../../hooks/useLanguage"; 4 | import Link from "next/link"; 5 | 6 | const SocialPart = () => { 7 | const { t } = useLanguage(); 8 | 9 | return ( 10 |
11 |
12 |

{t.beWithUs}

13 |
14 | {socialMedia.map((SocialItem) => { 15 | return ( 16 | 17 | 21 | 27 | 28 | 29 | ); 30 | })} 31 |
32 |
33 |
34 |

{t.emailRegister}

35 |
{ 38 | e.preventDefault(); 39 | }} 40 | > 41 | 46 | 52 |
53 |
54 |
55 | ); 56 | }; 57 | 58 | export default SocialPart; 59 | -------------------------------------------------------------------------------- /components/header/language/Language.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { settingBoxActions } from "../../../store/settingBox-slice"; 4 | import { MdLanguage } from "react-icons/md"; 5 | import { useLanguage } from "../../../hooks/useLanguage"; 6 | import LanguageItem from "./LanguageItem"; 7 | 8 | const Language = () => { 9 | const { t, locale } = useLanguage(); 10 | const dispatch = useDispatch(); 11 | const [openLang, setOpenLang] = useState(false); 12 | 13 | useEffect(() => { 14 | document.documentElement.dir = locale === "en" ? "ltr" : "rtl"; 15 | }, [locale]); 16 | 17 | function onCloseLangBox(isOpen: boolean) { 18 | setOpenLang(isOpen); 19 | } 20 | 21 | return ( 22 |
23 |
24 |

{t.language}

25 |
26 | dispatch(settingBoxActions.closeSettingBox())} 29 | /> 30 | dispatch(settingBoxActions.closeSettingBox())} 33 | /> 34 |
35 |
36 | 37 |
setOpenLang((prevState) => !prevState)} 40 | > 41 |

42 | {locale === "en" ? "En" : "Fa"} 43 |

44 | 45 |
46 | {openLang ? ( 47 | <> 48 |
setOpenLang(false)} 51 | >
52 |
55 | 56 | 57 |
58 | 59 | ) : null} 60 |
61 | ); 62 | }; 63 | 64 | export default Language; 65 | -------------------------------------------------------------------------------- /components/footer/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useLanguage } from "../../hooks/useLanguage"; 3 | import FooterColumns from "./footerContent/FooterColumns"; 4 | import SocialPart from "./footerContent/SocialPart"; 5 | import { BsFillSuitHeartFill } from "react-icons/bs"; 6 | import { RiDoubleQuotesL, RiDoubleQuotesR } from "react-icons/ri"; 7 | 8 | const Footer = () => { 9 | const { t, locale } = useLanguage(); 10 | const StartQuot = locale === "en" ? RiDoubleQuotesL : RiDoubleQuotesR; 11 | const EndQuot = locale === "en" ? RiDoubleQuotesR : RiDoubleQuotesL; 12 | return ( 13 |
14 |
15 |
16 | 17 | 18 |
19 |
20 |
21 |
22 | {t.copyRight} 23 | 31 | {locale === "en" ? "by Zahra Mirzaei" : "توسط زهرا میرزایی"} 32 |
33 |
34 | 42 | {t.cafeDX} 43 | 51 |   52 | 58 | CafeDX 59 | 60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default Footer; 67 | -------------------------------------------------------------------------------- /components/UI/Input.tsx: -------------------------------------------------------------------------------- 1 | import React, { useImperativeHandle, useRef, useState } from "react"; 2 | import { useLanguage } from "../../hooks/useLanguage"; 3 | 4 | interface Props { 5 | id: string; 6 | type: string; 7 | minLength?: number; 8 | maxLength?: number; 9 | placeholder?: string; 10 | classes?: string; 11 | value?: string; 12 | ref?: HTMLInputElement; 13 | readonly?: boolean; 14 | autocomplete?: string; 15 | title?: string; 16 | required?: boolean; 17 | } 18 | 19 | interface IImperativeHandler { 20 | focus: () => void; 21 | value?: string; 22 | } 23 | const Input = React.forwardRef((props, ref) => { 24 | const inputRef = useRef(null); 25 | const [value, setValue] = useState(props.value || ""); 26 | 27 | function inputChangeHandler(e: React.FormEvent) { 28 | setValue(e.currentTarget.value); 29 | } 30 | 31 | function inputFocused() { 32 | inputRef.current?.focus(); 33 | inputRef.current?.setAttribute("style", "border:2px solid red"); 34 | } 35 | 36 | useImperativeHandle(ref, () => { 37 | return { 38 | focus: inputFocused, 39 | value: value, 40 | }; 41 | }); 42 | const { t } = useLanguage(); 43 | return ( 44 |
45 | 54 | 69 |
70 | ); 71 | }); 72 | 73 | Input.displayName = "Input"; 74 | export default Input; 75 | -------------------------------------------------------------------------------- /components/cart/OrderSummaryBox.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import React from "react"; 3 | import { useSelector } from "react-redux"; 4 | import { useLanguage } from "../../hooks/useLanguage"; 5 | import { ICartRootState } from "../../lib/types/cart"; 6 | import ProductPrice from "../UI/ProductPrice"; 7 | import { changeNumbersFormatEnToFa } from "../../utilities/changeNumbersFormatEnToFa"; 8 | 9 | const OrderSummaryBox = () => { 10 | const { t, locale } = useLanguage(); 11 | const totalAmount = useSelector( 12 | (state: ICartRootState) => state.cart.totalAmount 13 | ); 14 | const totalQuantity = useSelector( 15 | (state: ICartRootState) => state.cart.totalQuantity 16 | ); 17 | 18 | return ( 19 | <> 20 | {totalQuantity > 0 ? ( 21 |
22 |

{t.orderSummary}

23 |
24 |
25 |

26 | {t.totalQuantity} 27 |

28 |

29 | {locale === "en" 30 | ? totalQuantity 31 | : changeNumbersFormatEnToFa(totalQuantity)} 32 |

33 |
34 |
35 |

36 | {t.totalAmount} 37 |

38 | 39 |
40 |
41 | 42 | 43 | {t.order} 44 | 45 | 46 |
47 | ) : ( 48 |

49 | {t.cartIsEmpty} 50 |

51 | )} 52 | 53 | ); 54 | }; 55 | 56 | export default OrderSummaryBox; 57 | -------------------------------------------------------------------------------- /components/cart/CartIcon.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import Link from "next/link"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { Transition } from "react-transition-group"; 5 | import { AiOutlineShoppingCart } from "react-icons/ai"; 6 | import { cartUiActions } from "../../store/cartUI-slice"; 7 | import CartBox from "./CartBox"; 8 | import { ICartUiRootState, ICartRootState } from "../../lib/types/cart"; 9 | import { changeNumbersFormatEnToFa } from "../../utilities/changeNumbersFormatEnToFa"; 10 | import { useLanguage } from "../../hooks/useLanguage"; 11 | 12 | const Basket = () => { 13 | const dispatch = useDispatch(); 14 | const { locale } = useLanguage(); 15 | const showCartBox = useSelector( 16 | (state: ICartUiRootState) => state.cartUi.cartBoxIsVisible 17 | ); 18 | const cartItemQuantity = useSelector( 19 | (state: ICartRootState) => state.cart.totalQuantity 20 | ); 21 | 22 | const nodeRef = useRef(null); 23 | 24 | function onMouseHoverHandler(toggle: boolean) { 25 | dispatch(cartUiActions.toggleCartBox(toggle)); 26 | } 27 | 28 | return ( 29 |
onMouseHoverHandler(true)} 32 | onMouseOut={() => onMouseHoverHandler(false)} 33 | > 34 | 35 | 36 | 37 | 38 | {locale === "en" 39 | ? cartItemQuantity 40 | : changeNumbersFormatEnToFa(cartItemQuantity)} 41 | 42 | 43 | 44 | 51 | {(state) => { 52 | return ( 53 |
54 | 55 |
56 | ); 57 | }} 58 |
59 |
60 | ); 61 | }; 62 | 63 | export default Basket; 64 | -------------------------------------------------------------------------------- /components/header/menu/MegaMenu/MegaMenu.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { megaMenuActions } from "../../../../store/megaMenu-slice"; 4 | import { Transition } from "react-transition-group"; 5 | import { useLanguage } from "../../../../hooks/useLanguage"; 6 | import { GoGrabber } from "react-icons/go"; 7 | import MenusContainer from "./MenusContainer"; 8 | import { IMegaMenuRootState } from "../../../../lib/types/megaMenu"; 9 | 10 | const MegaMenu = () => { 11 | const nodeRef = useRef(null); 12 | const { t } = useLanguage(); 13 | const dispatch = useDispatch(); 14 | function showMegaMenuHandler() { 15 | dispatch(megaMenuActions.openMegaMenu()); 16 | } 17 | function closeMegaMenuHandler() { 18 | dispatch(megaMenuActions.closeMegaMenu()); 19 | } 20 | const isMegaMenuOpen = useSelector( 21 | (state: IMegaMenuRootState) => state.megaMenu.isMegaMenuOpen 22 | ); 23 | 24 | return ( 25 |
30 |
31 | 32 |

{t.CategoryOfGoods}

33 |
34 | 35 | 42 | {(state) => { 43 | return ( 44 |
45 |
57 |
58 | 59 |
60 |
61 | ); 62 | }} 63 |
64 |
65 | ); 66 | }; 67 | 68 | export default MegaMenu; 69 | -------------------------------------------------------------------------------- /components/banners/banner-box/BannerBox.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import React from "react"; 3 | import DiscountCountdown from "../../UI/discountCountdown/DiscountCountdown"; 4 | import { useLanguage } from "../../../hooks/useLanguage"; 5 | import Link from "next/link"; 6 | import { useWindowDimensions } from "../../../hooks/useWindowDimensions"; 7 | 8 | interface Props { 9 | title: string; 10 | description: string; 11 | imgSrc: string; 12 | imgWidth: number; 13 | imgHeight: number; 14 | numberOfDiscountDate: number; 15 | buttonText: string; 16 | href: string; 17 | } 18 | const BannerBox: React.FC = ({ 19 | title, 20 | description, 21 | imgSrc, 22 | imgWidth, 23 | imgHeight, 24 | numberOfDiscountDate, 25 | buttonText, 26 | href, 27 | }) => { 28 | const { t } = useLanguage(); 29 | const { width } = useWindowDimensions(); 30 | let imageWidth = width >= 2000 ? 1300 : imgWidth; 31 | return ( 32 |
33 | {title} 40 | 41 |
42 |

43 | {t[`${title}`]} 44 |

45 |

46 | {t[`${description}`]} 47 |

48 | 49 | 50 | {t[`${buttonText}`]} 51 | 52 | 53 |
54 | 55 |
56 | ); 57 | }; 58 | 59 | export default BannerBox; 60 | -------------------------------------------------------------------------------- /components/UI/Breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useRouter } from "next/router"; 3 | import Link from "next/link"; 4 | import { IBreadcrumb } from "../../lib/types/breadcrumb"; 5 | import { useLanguage } from "../../hooks/useLanguage"; 6 | import { BsShop } from "react-icons/bs"; 7 | 8 | const convertBreadcrumb = (str: string) => { 9 | return str 10 | .replace(/-/g, " ") 11 | .replace(/oe/g, "ö") 12 | .replace(/ae/g, "ä") 13 | .replace(/ue/g, "ü"); 14 | }; 15 | 16 | const Breadcrumb = () => { 17 | const { t } = useLanguage(); 18 | const router = useRouter(); 19 | const [breadcrumbs, setBreadcrumbs] = useState([]); 20 | useEffect(() => { 21 | if (router) { 22 | const paths = router.asPath.split("/"); 23 | paths.shift(); 24 | 25 | const pathsArray = paths.map((path, i) => { 26 | return { 27 | breadcrumb: path, 28 | href: "/" + paths.slice(0, i + 1).join("/"), 29 | }; 30 | }); 31 | 32 | setBreadcrumbs(pathsArray); 33 | } 34 | }, [router]); 35 | 36 | if (!breadcrumbs) { 37 | return null; 38 | } 39 | 40 | return ( 41 |
42 | 70 |
71 | ); 72 | }; 73 | 74 | export default Breadcrumb; 75 | -------------------------------------------------------------------------------- /components/productDetails/DetailsSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import StarRatingComponent from "react-star-rating-component"; 3 | import { useLanguage } from "../../hooks/useLanguage"; 4 | import { IProduct } from "../../lib/types/products"; 5 | import CallToAction from "./CallToAction"; 6 | 7 | interface Props { 8 | product: IProduct; 9 | } 10 | const DetailsSection: React.FC = ({ product }) => { 11 | const { t } = useLanguage(); 12 | 13 | return ( 14 |
15 |

16 | {product.name} 17 |

18 |
19 |
20 |
21 |
22 | 27 |

28 | {product.starRating} {t.stars} 29 |

30 |
31 |

{t.details}

32 |
33 | {Object.keys(product.details!).map((key) => { 34 | const detailsValue = Array.isArray(product.details![key]) 35 | ? [...product.details![key]].join(" - ") 36 | : product.details![key] === true 37 | ? t.true 38 | : product.details![key] === false 39 | ? t.false 40 | : product.details![key]; 41 | 42 | return ( 43 |
44 |
45 | {t[key]} 46 |
47 | : 48 |

52 | {detailsValue} 53 |

54 |
55 | ); 56 | })} 57 |
58 |
59 | 60 |
61 |
62 | ); 63 | }; 64 | 65 | export default DetailsSection; 66 | -------------------------------------------------------------------------------- /components/UI/card/CardActions.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { useTheme } from "next-themes"; 4 | import { cartActions } from "../../../store/cart-slice"; 5 | import { favoriteActions } from "../../../store/favorite-slice"; 6 | import { 7 | RiHeartFill, 8 | RiHeartAddLine, 9 | RiShareLine, 10 | RiShoppingCart2Line, 11 | } from "react-icons/ri"; 12 | import { IProduct } from "../../../lib/types/products"; 13 | import { IFavoriteRootState } from "../../../lib/types/favorite"; 14 | 15 | import { toast } from "react-toastify"; 16 | import { useLanguage } from "../../../hooks/useLanguage"; 17 | 18 | interface Props { 19 | product: IProduct; 20 | } 21 | 22 | const CardActions: React.FC = ({ product }) => { 23 | const dispatch = useDispatch(); 24 | const { t } = useLanguage(); 25 | const { theme } = useTheme(); 26 | 27 | const favoriteItems = useSelector( 28 | (state: IFavoriteRootState) => state.favorite.items 29 | ); 30 | const isInFavorite = favoriteItems.some( 31 | (item) => item.slug.current === product.slug.current 32 | ); 33 | const FavoriteIcon = isInFavorite ? RiHeartFill : RiHeartAddLine; 34 | 35 | function addToCartHandler() { 36 | dispatch(cartActions.addItemToCart({ product: product, quantity: 1 })); 37 | toast.success(t.productAddedToCartMsg, { 38 | theme: theme === "dark" ? "dark" : "light", 39 | }); 40 | } 41 | 42 | function toggleFavoriteHandler() { 43 | !isInFavorite 44 | ? dispatch(favoriteActions.addToFavorite(product)) 45 | : dispatch(favoriteActions.removeFromFavorite(product.slug.current)); 46 | } 47 | 48 | return ( 49 |
50 |
54 | 60 |
61 |
62 | 63 |
64 |
68 | 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default CardActions; 79 | -------------------------------------------------------------------------------- /components/productList/ProductList.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useLanguage } from "../../hooks/useLanguage"; 3 | import { IProduct } from "../../lib/types/products"; 4 | import SubmenuCategory from "./SubmenuCategory"; 5 | import Card from "../UI/card/Card"; 6 | import Breadcrumb from "../UI/Breadcrumb"; 7 | import Sort from "./Sort"; 8 | import { useDispatch, useSelector } from "react-redux"; 9 | import { SortedProductsListActions } from "../../store/sortedProductList-slice"; 10 | import { useRouter } from "next/router"; 11 | import { IProductListRootState } from "../../lib/types/productList"; 12 | 13 | interface Props { 14 | productList: IProduct[]; 15 | } 16 | const ProductList: React.FC = ({ productList }) => { 17 | const router = useRouter(); 18 | const { t } = useLanguage(); 19 | let isInNewestProductsPage = 20 | router.pathname === "/newestProducts" ? true : false; 21 | 22 | const [selectedRadioBtn, setSelectedRadioBtn] = useState("all"); 23 | const dispatch = useDispatch(); 24 | 25 | useEffect(() => { 26 | dispatch( 27 | SortedProductsListActions.sortProductsList({ 28 | productsList: productList, 29 | sortBasedOn: selectedRadioBtn, 30 | }) 31 | ); 32 | }, [dispatch, productList, selectedRadioBtn]); 33 | 34 | const sortedProductList = useSelector( 35 | (state: IProductListRootState) => state.sortedProductsList.productsList 36 | ); 37 | 38 | function onChangeHandler(e: React.ChangeEvent) { 39 | setSelectedRadioBtn(e.currentTarget.id); 40 | } 41 | 42 | return ( 43 |
44 | 45 | 46 |
47 | {isInNewestProductsPage && productList.length ? ( 48 |
49 | {productList 50 | ? productList.map((product: IProduct) => { 51 | return ; 52 | }) 53 | : null} 54 |
55 | ) : sortedProductList && sortedProductList.length ? ( 56 |
57 | 61 |
62 | {sortedProductList.map((product: IProduct) => { 63 | return ; 64 | })} 65 |
66 |
67 | ) : ( 68 |

{t.noProduct}

69 | )} 70 |
71 |
72 | ); 73 | }; 74 | 75 | export default ProductList; 76 | -------------------------------------------------------------------------------- /mock/brand.js: -------------------------------------------------------------------------------- 1 | export const brandContent = [ 2 | { 3 | id: 1, 4 | name: "adidas", 5 | imgSrc: "/images/brand-logo-img/adidas.webp", 6 | }, 7 | { 8 | id: 2, 9 | name: "apple", 10 | imgSrc: "/images/brand-logo-img/apple.webp", 11 | }, 12 | { 13 | id: 3, 14 | name: "asus", 15 | imgSrc: "/images/brand-logo-img/asus.webp", 16 | }, 17 | { 18 | id: 4, 19 | name: "benq", 20 | imgSrc: "/images/brand-logo-img/benq.webp", 21 | }, 22 | { 23 | id: 5, 24 | name: "bvlgari", 25 | imgSrc: "/images/brand-logo-img/bvlgari.webp", 26 | }, 27 | { 28 | id: 6, 29 | name: "del", 30 | imgSrc: "/images/brand-logo-img/del.webp", 31 | }, 32 | { 33 | id: 7, 34 | name: "dior", 35 | imgSrc: "/images/brand-logo-img/dior.webp", 36 | }, 37 | { 38 | id: 8, 39 | name: "dolce-gabbana", 40 | imgSrc: "/images/brand-logo-img/dolce-gabbana.webp", 41 | }, 42 | { 43 | id: 9, 44 | name: "gucci", 45 | imgSrc: "/images/brand-logo-img/gucci.webp", 46 | }, 47 | { 48 | id: 10, 49 | name: "hp", 50 | imgSrc: "/images/brand-logo-img/hp.webp", 51 | }, 52 | { 53 | id: 11, 54 | name: "lg", 55 | imgSrc: "/images/brand-logo-img/lg.webp", 56 | }, 57 | { 58 | id: 12, 59 | name: "loreal", 60 | imgSrc: "/images/brand-logo-img/loreal.webp", 61 | }, 62 | { 63 | id: 13, 64 | name: "louis-vuitton", 65 | imgSrc: "/images/brand-logo-img/louis-vuitton.webp", 66 | }, 67 | { 68 | id: 14, 69 | name: "mac", 70 | imgSrc: "/images/brand-logo-img/mac.webp", 71 | }, 72 | { 73 | id: 15, 74 | name: "maybelline", 75 | imgSrc: "/images/brand-logo-img/maybelline.webp", 76 | }, 77 | { 78 | id: 16, 79 | name: "msi", 80 | imgSrc: "/images/brand-logo-img/msi.webp", 81 | }, 82 | { 83 | id: 17, 84 | name: "patek-philippe", 85 | imgSrc: "/images/brand-logo-img/patek-philippe.webp", 86 | }, 87 | { 88 | id: 18, 89 | name: "puma", 90 | imgSrc: "/images/brand-logo-img/puma.webp", 91 | }, 92 | { 93 | id: 19, 94 | name: "rolex", 95 | imgSrc: "/images/brand-logo-img/rolex.webp", 96 | }, 97 | { 98 | id: 20, 99 | name: "samsung", 100 | imgSrc: "/images/brand-logo-img/samsung.webp", 101 | }, 102 | { 103 | id: 21, 104 | name: "sony", 105 | imgSrc: "/images/brand-logo-img/sony.webp", 106 | }, 107 | { 108 | id: 22, 109 | name: "toshiba", 110 | imgSrc: "/images/brand-logo-img/toshiba.webp", 111 | }, 112 | { 113 | id: 23, 114 | name: "versace", 115 | imgSrc: "/images/brand-logo-img/versace.webp", 116 | }, 117 | { 118 | id: 24, 119 | name: "xiaomi", 120 | imgSrc: "/images/brand-logo-img/xiaomi.webp", 121 | }, 122 | ]; 123 | -------------------------------------------------------------------------------- /components/UI/card/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import StarRatingComponent from "react-star-rating-component"; 5 | import { IProduct } from "../../../lib/types/products"; 6 | import { urlFor } from "../../../lib/client"; 7 | import CardActions from "./CardActions"; 8 | import ProductPrice from "../ProductPrice"; 9 | 10 | interface Props { 11 | product: IProduct; 12 | } 13 | 14 | const Card: React.FC = ({ product }) => { 15 | return ( 16 | 62 | ); 63 | }; 64 | 65 | export default Card; 66 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | function withOpacity(variableName) { 3 | return ({ opacityValue }) => { 4 | if (opacityValue !== undefined) { 5 | return `rgba(var(${variableName}), ${opacityValue})`; 6 | } 7 | return `rgba(var(${variableName}))`; 8 | }; 9 | } 10 | 11 | module.exports = { 12 | darkMode: "class", 13 | mode: "jit", 14 | content: [ 15 | "./pages/**/*.{js,ts,jsx,tsx}", 16 | "./components/**/*.{js,ts,jsx,tsx}", 17 | ], 18 | theme: { 19 | extend: { 20 | colors: { 21 | palette: { 22 | primary: withOpacity("--color-primary"), 23 | secondary: withOpacity("--color-secondary"), 24 | }, 25 | }, 26 | textColor: { 27 | palette: { 28 | base: withOpacity("--color-text-base"), 29 | mute: withOpacity("--color-text-muted"), 30 | side: withOpacity("--color-text-side"), 31 | }, 32 | }, 33 | backgroundColor: { 34 | palette: { 35 | fill: withOpacity("--color-bg"), 36 | card: withOpacity("--color-bg-side"), 37 | dark: withOpacity("--color-bg-dark"), 38 | digitalCategory: "var(--digital-category-bgc)", 39 | fashionCategory: "var(--fashion-category-bgc)", 40 | beautyCategory: "var( --beauty-category-bgc)", 41 | sportCategory: "var(--sport-category-bgc)", 42 | houseCategory: "var(--house-category-bgc)", 43 | toyCategory: "var(--toy-category-bgc)", 44 | stationeryCategory: "var(--stationery-category-bgc)", 45 | }, 46 | }, 47 | fontFamily: { 48 | farsi: "'iranyekan', 'IRANSans', 'Tahoma'", 49 | english: "'Poppins', 'Roboto', 'sans-serif'", 50 | }, 51 | keyframes: { 52 | sidenavLTR: { 53 | "0%": { transform: "translateX(-100%)" }, 54 | "100%": { transform: "translateX(0px)" }, 55 | }, 56 | sidenavRTL: { 57 | "0%": { transform: "translateX(100%)" }, 58 | "100%": { transform: "translateX(0px)" }, 59 | }, 60 | fade: { 61 | "0%": { opacity: 0 }, 62 | "100%": { opacity: 1 }, 63 | }, 64 | dropDown: { 65 | "0%": { opacity: 0, transform: "scaleY(0)" }, 66 | "100%": { opacity: 1, transform: "scaleY(1)" }, 67 | }, 68 | }, 69 | animation: { 70 | sidenavLTREntering: "sidenavLTR 0.3s ease-in-out forwards", 71 | sidenavRTLEntering: "sidenavRTL 0.3s ease-in-out forwards", 72 | sidenavLTRExit: "sidenavLTR 0.3s ease-in-out reverse forwards", 73 | sidenavRTLExit: "sidenavRTL 0.3s ease-in-out reverse forwards", 74 | fadeEntering: "fade 0.3s forwards", 75 | fadeExit: "fade 0.3s reverse forwards", 76 | dropDown: "dropDown 0.3s forwards", 77 | dropDownExit: "dropDown 0.3s reverse forwards", 78 | }, 79 | backgroundImage: { 80 | offersBG: "url('/images/carouselBox-bg/offersbg.webp')", 81 | }, 82 | }, 83 | }, 84 | plugins: [], 85 | }; 86 | -------------------------------------------------------------------------------- /mock/menuItems.js: -------------------------------------------------------------------------------- 1 | import { BsLaptop, BsBook } from "react-icons/bs"; 2 | import { IoShirtOutline, IoShirtSharp } from "react-icons/io5"; 3 | import { MdOutlineToys } from "react-icons/md"; 4 | import { RiHeartPulseLine, RiFireLine } from "react-icons/ri"; 5 | import { AiOutlineHome, AiOutlinePercentage } from "react-icons/ai"; 6 | import { BiFootball } from "react-icons/bi"; 7 | 8 | import { ImMobile } from "react-icons/im"; 9 | import { FiMonitor, FiHeadphones } from "react-icons/fi"; 10 | 11 | import { GiLargeDress } from "react-icons/gi"; 12 | import { FaBaby, FaRedhat } from "react-icons/fa"; 13 | 14 | const menuItems = [ 15 | { 16 | category: "digital", 17 | icon: BsLaptop, 18 | productsGroup: [ 19 | { 20 | title: "laptop", 21 | icon: BsLaptop, 22 | subtitles: [ 23 | "asus", 24 | "apple", 25 | "dell", 26 | "lenovo", 27 | "samsung", 28 | "hp", 29 | "huawei", 30 | "acer", 31 | "msi", 32 | ], 33 | }, 34 | { 35 | title: "mobile", 36 | icon: ImMobile, 37 | subtitles: [ 38 | "samsung", 39 | "apple", 40 | "nokia", 41 | "xiaomi", 42 | "motorola", 43 | "lg", 44 | "sony", 45 | ], 46 | }, 47 | { 48 | title: "computer", 49 | icon: FiMonitor, 50 | subtitles: ["monitor", "mouse", "keyboard", "hard"], 51 | }, 52 | { 53 | title: "other", 54 | icon: FiHeadphones, 55 | subtitles: ["tablet", "powerBank", "speaker", "headphones"], 56 | }, 57 | ], 58 | }, 59 | { 60 | category: "fashion", 61 | icon: IoShirtOutline, 62 | productsGroup: [ 63 | { 64 | title: "women", 65 | icon: GiLargeDress, 66 | subtitles: [ 67 | "dress", 68 | "skirt", 69 | "jeans", 70 | "pants", 71 | "tShirt", 72 | "shoes", 73 | "scarf", 74 | ], 75 | }, 76 | { 77 | title: "men", 78 | icon: IoShirtSharp, 79 | subtitles: ["shirt", "pants", "tie", "tShirt", "shoes", "jeans"], 80 | }, 81 | { 82 | title: "child", 83 | icon: FaBaby, 84 | subtitles: ["overalls", "mittens", "babyApron", "shoes", "tShirt"], 85 | }, 86 | { 87 | title: "other", 88 | icon: FaRedhat, 89 | subtitles: ["watch", "wallet", "hat", "belt"], 90 | }, 91 | ], 92 | }, 93 | { category: "toys", icon: MdOutlineToys }, 94 | { category: "cosmetic", icon: RiHeartPulseLine }, 95 | { category: "home", icon: AiOutlineHome }, 96 | { category: "sport", icon: BiFootball }, 97 | { category: "stationery", icon: BsBook }, 98 | ]; 99 | 100 | export default menuItems; 101 | 102 | export const extraMenu = [ 103 | { title: "offer", icon: AiOutlinePercentage, href: "/offers" }, 104 | { title: "bestSells", icon: RiFireLine, href: "/" }, 105 | ]; 106 | -------------------------------------------------------------------------------- /components/UI/DropDown.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, forwardRef, useRef } from "react"; 2 | import Link from "next/link"; 3 | import { HiChevronUp, HiChevronDown } from "react-icons/hi"; 4 | import { Transition } from "react-transition-group"; 5 | 6 | import { IDropDown } from "../../lib/types/dropDown"; 7 | import { useDispatch } from "react-redux"; 8 | import { sideNavBarActions } from "../../store/sideNavBar-slice"; 9 | import { useSelector } from "react-redux"; 10 | import { IActiveMenuItemRootState } from "../../lib/types/activeMenuItem"; 11 | import { useLanguage } from "../../hooks/useLanguage"; 12 | 13 | interface Props { 14 | dropDown: IDropDown; 15 | ref: React.HTMLProps; 16 | } 17 | const DropDown = forwardRef(({ dropDown }, ref) => { 18 | const [openDropdown, setOpenDropDown] = useState(false); 19 | const nodeRef = useRef(null); 20 | const dispatch = useDispatch(); 21 | const { t } = useLanguage(); 22 | let ArrowDirection = !openDropdown ? HiChevronDown : HiChevronUp; 23 | 24 | const activeMenuItemText = useSelector( 25 | (state: IActiveMenuItemRootState) => state.activeMenuItem.activeMenuItemText 26 | ); 27 | 28 | const closeNavbar = () => { 29 | dispatch(sideNavBarActions.closeNavbar()); 30 | }; 31 | 32 | return ( 33 |
34 |
setOpenDropDown((prevState) => !prevState)} 37 | > 38 |

39 | {t[`${dropDown.title}`]} 40 |

41 | 42 |
43 | 50 | {(state) => ( 51 | <> 52 |
64 | {dropDown.subtitles.map((item, index) => { 65 | return ( 66 |
71 | 74 | 75 |
{t[`${item}`]}
76 |
77 | 78 |
79 | ); 80 | })} 81 |
82 | 83 | )} 84 |
85 |
86 | ); 87 | }); 88 | 89 | DropDown.displayName = "DropDown"; 90 | export default DropDown; 91 | -------------------------------------------------------------------------------- /components/productDetails/CallToAction.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { useTheme } from "next-themes"; 3 | import { HiOutlinePlusSm, HiMinusSm } from "react-icons/hi"; 4 | import { BsCartPlus } from "react-icons/bs"; 5 | import { useLanguage } from "../../hooks/useLanguage"; 6 | import { useDispatch } from "react-redux"; 7 | import { cartActions } from "../../store/cart-slice"; 8 | import { IProduct } from "../../lib/types/products"; 9 | import ProductPrice from "../UI/ProductPrice"; 10 | import { toast } from "react-toastify"; 11 | 12 | interface Props { 13 | product: IProduct; 14 | } 15 | const CallToAction: React.FC = ({ product }) => { 16 | const { price, discount } = product; 17 | const [counter, setCounter] = useState(1); 18 | const { t } = useLanguage(); 19 | const { theme } = useTheme(); 20 | 21 | useEffect(() => { 22 | return () => { 23 | setCounter(1); 24 | }; 25 | }, [product]); 26 | 27 | const dispatch = useDispatch(); 28 | 29 | function addToCartHandler() { 30 | dispatch( 31 | cartActions.addItemToCart({ 32 | product: product, 33 | quantity: counter, 34 | }) 35 | ); 36 | toast.success(t.productAddedToCartMsg, { 37 | theme: theme === "dark" ? "dark" : "light", 38 | }); 39 | } 40 | 41 | function increment() { 42 | if (counter < 10) { 43 | setCounter((prev) => prev + 1); 44 | } 45 | } 46 | function decrement() { 47 | if (counter > 1) { 48 | setCounter((prev) => prev - 1); 49 | } 50 | } 51 | 52 | function onInputNumberChangeHandler(e: React.ChangeEvent) { 53 | if (+e.currentTarget.value >= 1 && +e.currentTarget.value <= 10) { 54 | setCounter(+e.currentTarget.value); 55 | } 56 | } 57 | 58 | return ( 59 |
60 |
61 |

{t.price}

62 | 63 |
64 |
65 |
66 | 67 |
68 | 76 |
77 | 78 |
79 |
80 |
81 | 88 |
89 | ); 90 | }; 91 | 92 | export default CallToAction; 93 | -------------------------------------------------------------------------------- /store/cart-slice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, PayloadAction } from "@reduxjs/toolkit"; 2 | import { ICart } from "../lib/types/cart"; 3 | import { IProduct } from "../lib/types/products"; 4 | import { calculateDiscountPercentage } from "../utilities/calculateDiscountPercentage"; 5 | 6 | const initialState: ICart = { 7 | items: [], 8 | totalQuantity: 0, 9 | totalAmount: 0, 10 | }; 11 | 12 | const cartSlice = createSlice({ 13 | name: "cart", 14 | initialState, 15 | reducers: { 16 | addItemToCart( 17 | state: ICart, 18 | action: PayloadAction<{ product: IProduct; quantity: number }> 19 | ) { 20 | const newItem = action.payload.product; 21 | 22 | const existingItem = state.items.find( 23 | (item) => item.slug.current === newItem.slug.current 24 | ); 25 | 26 | state.totalQuantity = state.totalQuantity + action.payload.quantity; 27 | 28 | state.totalAmount = 29 | state.totalAmount + 30 | action.payload.quantity * 31 | (action.payload.product.discount 32 | ? calculateDiscountPercentage( 33 | action.payload.product.price, 34 | action.payload.product.discount 35 | ) 36 | : action.payload.product.price); 37 | 38 | if (!existingItem) { 39 | const totalPrice = 40 | (newItem.discount 41 | ? calculateDiscountPercentage(newItem.price, newItem.discount) 42 | : newItem.price) * action.payload.quantity; 43 | 44 | state.items.push({ 45 | ...newItem, 46 | quantity: action.payload.quantity, 47 | totalPrice, 48 | }); 49 | } else { 50 | const totalPrice = 51 | existingItem.totalPrice + 52 | (existingItem.discount 53 | ? calculateDiscountPercentage( 54 | existingItem.price, 55 | existingItem.discount 56 | ) * action.payload.quantity 57 | : existingItem.price * action.payload.quantity); 58 | 59 | existingItem.quantity += action.payload.quantity; 60 | existingItem.totalPrice = totalPrice; 61 | } 62 | }, 63 | 64 | removeItemFromCart( 65 | state: ICart, 66 | action: PayloadAction //slug.current as payload 67 | ) { 68 | const productSlug = action.payload; 69 | const existingItem = state.items.find( 70 | (item) => item.slug.current === productSlug 71 | ); 72 | 73 | state.totalQuantity--; 74 | 75 | state.totalAmount = 76 | state.totalAmount - 77 | (existingItem?.discount 78 | ? calculateDiscountPercentage( 79 | existingItem.price, 80 | existingItem.discount 81 | ) 82 | : existingItem?.price)!; 83 | 84 | if (existingItem?.quantity === 1) { 85 | state.items = state.items.filter( 86 | (item) => item.slug.current !== productSlug 87 | ); 88 | } else { 89 | existingItem!.quantity--; 90 | existingItem!.totalPrice = 91 | existingItem!.totalPrice - 92 | (existingItem?.discount 93 | ? calculateDiscountPercentage( 94 | existingItem.price, 95 | existingItem.discount 96 | ) 97 | : existingItem?.price)!; 98 | } 99 | }, 100 | 101 | clearCart(state) { 102 | state = initialState; 103 | }, 104 | }, 105 | }); 106 | 107 | export const cartActions = cartSlice.actions; 108 | 109 | export default cartSlice.reducer; 110 | -------------------------------------------------------------------------------- /components/header/menu/MegaMenu/SubMenu.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useLanguage } from "../../../../hooks/useLanguage"; 4 | import { IDropDown } from "../../../../lib/types/dropDown"; 5 | import { useDispatch, useSelector } from "react-redux"; 6 | import { megaMenuActions } from "../../../../store/megaMenu-slice"; 7 | import { IActiveMenuItemRootState } from "../../../../lib/types/activeMenuItem"; 8 | import { HiChevronRight, HiChevronLeft } from "react-icons/hi"; 9 | 10 | interface Props { 11 | subMenuItems: IDropDown[] | undefined; 12 | } 13 | const SubMenu: React.FC = ({ subMenuItems }) => { 14 | const { t, locale } = useLanguage(); 15 | const ArrowDirection = locale === "en" ? HiChevronRight : HiChevronLeft; 16 | const dispatch = useDispatch(); 17 | 18 | const activeMenuItemText = useSelector( 19 | (state: IActiveMenuItemRootState) => state.activeMenuItem.activeMenuItemText 20 | ); 21 | 22 | return ( 23 |
24 |
25 | {subMenuItems ? ( 26 | <> 27 | 28 | dispatch(megaMenuActions.closeMegaMenu())} 31 | > 32 | {t.seeAllProduct} 33 | 34 | 35 | 36 | 37 | ) : null} 38 |
39 |
40 |
41 | {subMenuItems && activeMenuItemText ? ( 42 | <> 43 | {subMenuItems.map((menuTitle, index) => { 44 | return ( 45 | 72 | ); 73 | })} 74 | 75 | ) : ( 76 |

77 | {t.noProduct} 78 |

79 | )} 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default SubMenu; 86 | -------------------------------------------------------------------------------- /components/UI/CarouselBox/CarouselBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | import { useLanguage } from "../../../hooks/useLanguage"; 4 | import { NextArrow, PrevArrow } from "./CarouselBoxArrows"; 5 | import Slider from "react-slick"; 6 | import { HiOutlineChevronLeft, HiOutlineChevronRight } from "react-icons/hi"; 7 | 8 | interface Props { 9 | title: string; 10 | className?: string; 11 | href?: string; 12 | children?: React.ReactNode; 13 | full?: boolean; 14 | } 15 | const CarouselBox: React.FC = ({ 16 | title, 17 | className, 18 | children, 19 | href, 20 | full, 21 | }) => { 22 | const { t } = useLanguage(); 23 | 24 | const settings = { 25 | className: ` px-4 ${full ? "bg-palette-fill" : "bg-[#37bccef9]"}`, 26 | infinite: true, 27 | speed: 600, 28 | centerPadding: "60px", 29 | slidesToShow: 5, 30 | slidesToScroll: 5, 31 | // initialSlide: 0, 32 | swipeToSlide: true, 33 | // rtl: true, 34 | nextArrow: , 35 | prevArrow: , 36 | responsive: [ 37 | { 38 | breakpoint: 1324, 39 | settings: { 40 | slidesToShow: 4, 41 | slidesToScroll: 4, 42 | }, 43 | }, 44 | { 45 | breakpoint: 1024, 46 | settings: { 47 | slidesToShow: 3, 48 | slidesToScroll: 3, 49 | }, 50 | }, 51 | { 52 | breakpoint: 768, 53 | settings: { 54 | slidesToShow: 2, 55 | slidesToScroll: 2, 56 | }, 57 | }, 58 | { 59 | breakpoint: 640, 60 | settings: { 61 | slidesToShow: 1, 62 | slidesToScroll: 1, 63 | }, 64 | }, 65 | ], 66 | }; 67 | 68 | return ( 69 |
74 |
77 |

84 | {t[`${title}`]} 85 |

86 | {!full ? ( 87 | 88 | 89 | {t.seeAll} 90 | 91 | 92 | ) : null} 93 |
94 |
99 | {children} 100 |
101 |
102 | 103 |
104 |
105 | 106 |
107 |
108 |
109 |
110 | ); 111 | }; 112 | 113 | export default CarouselBox; 114 | -------------------------------------------------------------------------------- /components/favorite/FavoriteItem.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Image from "next/image"; 3 | import Link from "next/link"; 4 | import { useDispatch } from "react-redux"; 5 | import { toast } from "react-toastify"; 6 | import { favoriteActions } from "../../store/favorite-slice"; 7 | import { cartActions } from "../../store/cart-slice"; 8 | import { useLanguage } from "../../hooks/useLanguage"; 9 | import { urlFor } from "../../lib/client"; 10 | import ProductPrice from "../UI/ProductPrice"; 11 | import { BsCartPlus } from "react-icons/bs"; 12 | import { HiOutlineTrash } from "react-icons/hi"; 13 | import { IProduct } from "../../lib/types/products"; 14 | import { useTheme } from "next-themes"; 15 | 16 | interface Props { 17 | product: IProduct; 18 | } 19 | const FavoriteItem: React.FC = ({ product }) => { 20 | const { t } = useLanguage(); 21 | const { theme } = useTheme(); 22 | const dispatch = useDispatch(); 23 | 24 | function onRemoveFavoriteItem(productSlug: string) { 25 | dispatch(favoriteActions.removeFromFavorite(productSlug)); 26 | } 27 | 28 | function onAddToCart(product: IProduct) { 29 | dispatch(cartActions.addItemToCart({ product: product, quantity: 1 })); 30 | toast.success(t.productAddedToCartMsg, { 31 | theme: theme === "dark" ? "dark" : "light", 32 | }); 33 | } 34 | return ( 35 |
36 | 39 | 40 |
41 | {product?.image[0] && ( 42 | laptop image 49 | )} 50 | {product.discount ? ( 51 | 52 | discount-icon 58 | 59 | ) : null} 60 |
61 |
62 |

{product?.name}

63 | 64 |
65 |
66 | 67 |
68 | 77 | 86 |
87 |
88 | ); 89 | }; 90 | 91 | export default FavoriteItem; 92 | -------------------------------------------------------------------------------- /mock/category-lg.js: -------------------------------------------------------------------------------- 1 | export const categoryLgContent = [ 2 | { 3 | name: "digital", 4 | title: "digitalCategoryTitle", 5 | description: "digitalCategoryDescription", 6 | styles: { 7 | backgroundColor: "var(--digital-category-bgc)", 8 | flexDirection: "row", 9 | paddingBlock: "2rem", 10 | paddingInline: "1rem", 11 | gridRow: "span 6 / span 6", 12 | gridColumn: "span 3 / span 3", 13 | }, 14 | href: "/digital", 15 | imgSrc: "/images/category-img/digital-category.webp", 16 | imgWidth: 190, 17 | imgHeight: 240, 18 | }, 19 | { 20 | name: "fashion", 21 | title: "fashionCategoryTitle", 22 | description: "fashionCategoryDescription", 23 | styles: { 24 | backgroundColor: "var(--fashion-category-bgc)", 25 | flexDirection: "row", 26 | paddingInline: "1rem", 27 | paddingBlock: "unset", 28 | gridRow: "span 6 / span 6", 29 | gridColumn: "span 3 / span 3", 30 | }, 31 | href: "/fashion", 32 | imgSrc: "/images/category-img/fashion-category.webp", 33 | imgWidth: 240, 34 | imgHeight: 250, 35 | }, 36 | { 37 | name: "beauty", 38 | title: "beautyCategoryTitle", 39 | description: "beautyCategoryDescription", 40 | styles: { 41 | backgroundColor: "var(--beauty-category-bgc)", 42 | flexDirection: "row", 43 | paddingInline: "1rem", 44 | paddingBlock: "0.5rem", 45 | gridRow: "span 3 / span 3", 46 | gridColumn: "span 3 / span 3", 47 | }, 48 | href: "/beauty", 49 | imgSrc: "/images/category-img/beauty-category.webp", 50 | imgWidth: 170, 51 | imgHeight: 150, 52 | }, 53 | { 54 | name: "sport", 55 | title: "sportCategoryTitle", 56 | description: "sportCategoryDescription", 57 | styles: { 58 | backgroundColor: "var(--sport-category-bgc)", 59 | flexDirection: "row-reverse", 60 | paddingInline: "unset", 61 | paddingBlock: "0.5rem", 62 | gridRow: "span 3 / span 3", 63 | gridColumn: "span 3 / span 3", 64 | }, 65 | href: "/sport", 66 | imgSrc: "/images/category-img/sport-category.webp", 67 | imgWidth: 130, 68 | imgHeight: 150, 69 | }, 70 | { 71 | name: "house", 72 | title: "houseCategoryTitle", 73 | description: "houseCategoryDescription", 74 | styles: { 75 | backgroundColor: "var(--house-category-bgc)", 76 | flexDirection: "row", 77 | paddingInline: "1rem", 78 | paddingBlock: "unset", 79 | gridRow: "span 2 / span 2", 80 | gridColumn: "span 5 / span 5", 81 | }, 82 | href: "/house", 83 | imgSrc: "/images/category-img/house-category.webp", 84 | imgWidth: 320, 85 | imgHeight: 240, 86 | }, 87 | { 88 | name: "toy", 89 | title: "toyCategoryTitle", 90 | description: "toyCategoryDescription", 91 | styles: { 92 | backgroundColor: "var(--toy-category-bgc)", 93 | flexDirection: "column", 94 | paddingInline: "0.5rem", 95 | paddingBlock: "0.5rem", 96 | textAlign: "center", 97 | gridRow: "span 2 / span 2", 98 | gridColumn: "span 2 / span 2", 99 | }, 100 | href: "/toy", 101 | imgSrc: "/images/category-img/toy-category.webp", 102 | imgWidth: 130, 103 | imgHeight: 110, 104 | }, 105 | { 106 | name: "stationery", 107 | title: "stationeryCategoryTitle", 108 | description: "stationeryCategoryDescription", 109 | styles: { 110 | backgroundColor: "var(--stationery-category-bgc)", 111 | flexDirection: "row", 112 | paddingInline: "0.75rem", 113 | paddingBlock: "unset", 114 | gridRow: "span 2 / span 2", 115 | gridColumn: "span 2 / span 2", 116 | }, 117 | href: "/stationery", 118 | imgSrc: "/images/category-img/stationery-category.webp", 119 | imgWidth: 130, 120 | imgHeight: 250, 121 | imgCustomStyles: { alignSelf: "flex-start" }, 122 | }, 123 | ]; 124 | --------------------------------------------------------------------------------