(
20 | (
21 | {
22 | active,
23 | disabled,
24 | className = "",
25 | iconOnly,
26 | beforeContent,
27 | afterContent,
28 | size,
29 | variant = "primary",
30 | color = "primary",
31 | children,
32 | asChild = false,
33 | ...props
34 | },
35 | ref
36 | ) => {
37 | const Comp = asChild ? Slot : "button";
38 |
39 | const baseStyles =
40 | "transition-all duration-250 flex items-center justify-center border-none font-medium cursor-pointer rounded-lg gap-1 ";
41 | const sizeStyles = {
42 | sm: "py-1 px-2 text-sm",
43 | md: "py-2 px-4 text-base",
44 | lg: "py-3 px-6 text-lg",
45 | };
46 | const colorStyles = {
47 | primary:
48 | "bg-primary-bg-color hover:bg-primary-color-hover disabled:bg-green-700 text-black dark:text-white",
49 | black:
50 | "bg-black hover:bg-black/80 disabled:bg-stone-700 text-white dark:hover:bg-black/50",
51 | white:
52 | "bg-white hover:bg-white/50 disabled:bg-stone-700 text-black dark:hover:bg-white/80",
53 | info: "bg-blue-500 text-white border-blue-400 hover:bg-blue-600 active:bg-blue-700 disabled:bg-blue-200 disabled:text-stone-600",
54 | warning:
55 | "bg-yellow-500 text-white border-yellow-500 hover:bg-yellow-600 active:bg-yellow-700 disabled:bg-yellow-300",
56 | error:
57 | "bg-red-500 dark:bg-red-600 text-white border-red-500 dark:border-red-600 hover:bg-red-600 dark:hover:bg-red-700 active:bg-red-700 dark:active:bg-red-800 disabled:bg-red-300 dark:disabled:bg-red-400",
58 | };
59 | const variantStyles = {
60 | primary: "",
61 | secondary: "border-2",
62 | };
63 | const activeStyles = active ? "shadow-inner" : "";
64 | const iconOnlyStyles = iconOnly ? "p-1 aspect-square" : "";
65 | const disabledStyles = disabled ? "opacity-70 cursor-not-allowed" : "";
66 |
67 | const content = (
68 | <>
69 | {beforeContent}
70 | {children}
71 | {afterContent}
72 | >
73 | );
74 |
75 | return (
76 |
92 | {asChild ? React.Children.only(children) : content}
93 |
94 | );
95 | }
96 | );
97 |
98 | Button.displayName = "Button";
99 |
100 | export default Button;
101 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 | MarketKing
9 | My e-commerce web app, built using Next.js, is fully responsive and includes an admin dashboard, Stripe payment integration, and user authentication (login and sign-up pages). The app supports multiple languages (English and Arabic) and features a toggle for theme mode, offering an improved user experience and enhanced performance.
10 | live demo , (If you see an error message or run into an issue, please create bug report. thank you)
11 |
12 | ---
13 |
14 |
15 |

16 |
17 |
18 |

19 |
20 |
21 |

22 |
23 |
24 |

25 |
26 |
27 |
28 | ---
29 |
30 | ## Primary technologies employed
31 |
32 |
33 |
34 | ## Lighthouse project performance
35 |
36 |
37 |

38 |
39 |
40 |
41 | ## Building and running on localhost
42 |
43 | First install dependencies:
44 |
45 | ```sh
46 | npm install
47 | ```
48 |
49 | To run in hot module reloading mode:
50 |
51 | ```sh
52 | npm run dev
53 | ```
54 |
55 | To create a production build:
56 |
57 | ```sh
58 | npm run build
59 | ```
60 |
61 | ## App User Experience
62 | (note) If your internet speed is slow, please be patient as the GIF may take some time to download
63 |
64 |

65 |
66 |
67 | ---
68 |
69 |
70 |

71 |
72 |
--------------------------------------------------------------------------------
/app/_components/ui/Filter.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useTranslate } from "@/app/_hooks/useTranslate";
3 | import { usePathname, useRouter, useSearchParams } from "next/navigation";
4 |
5 | const sortArr = [
6 | { label: "All", name: "all" },
7 | { label: "Lowest", name: "lowest" },
8 | { label: "Highest", name: "highest" },
9 | ];
10 |
11 | const filterArr = [
12 | { label: "All", name: "all" },
13 | { label: "less-then-200", name: "less-then-200" },
14 | { label: "between-200-500", name: "between-200-500" },
15 | { label: "between-1000-5000", name: "between-1000-5000" },
16 | { label: "more-then-5000", name: "more-then-5000" },
17 | ];
18 |
19 | function Filter({ isMedium = false }: { isMedium?: boolean }) {
20 | const { t } = useTranslate();
21 | const searchParams = useSearchParams();
22 | const router = useRouter();
23 | const pathname = usePathname();
24 | const activeSort = searchParams.get("sort-price") || "all";
25 | const activeFilterPrice = searchParams.get("filter-price") || "all";
26 |
27 | const handleSort = (filter: string) => {
28 | const params = new URLSearchParams(searchParams);
29 | params.set("sort-price", filter);
30 | router.replace(`${pathname}?${params.toString()}`, { scroll: false });
31 | };
32 |
33 | const handleFilterPrice = (filter: string) => {
34 | const params = new URLSearchParams(searchParams);
35 | params.set("filter-price", filter);
36 | router.replace(`${pathname}?${params.toString()}`, { scroll: false });
37 | };
38 |
39 | return (
40 |
99 | );
100 | }
101 |
102 | export default Filter;
103 |
--------------------------------------------------------------------------------
/app/_components/marketking/CreateAccountForm.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import SubmitButton from "@/app/_components/ui/SubmitButton";
3 | import { useTranslate } from "@/app/_hooks/useTranslate";
4 | import MyLink from "../ui/MyLink";
5 | import { useFormState } from "react-dom";
6 | import { createAccount } from "@/app/_actions/createAccount";
7 | import ErrorMessage from "../ui/ErrorMessage";
8 | import { useEffect } from "react";
9 | import toast from "react-hot-toast";
10 |
11 | function CreateAccountForm() {
12 | const [state, formAction] = useFormState(createAccount, {});
13 | const { t } = useTranslate();
14 |
15 | useEffect(() => {
16 | if (state.success === false) {
17 | toast.error(t("error create email"));
18 | }
19 | }, [state.success, t]);
20 | return (
21 |
111 | );
112 | }
113 |
114 | export default CreateAccountForm;
115 |
--------------------------------------------------------------------------------
/app/[locale]/dashboard/orders/page.tsx:
--------------------------------------------------------------------------------
1 | import { useTranslate } from "@/app/_hooks/useTranslate";
2 | import prisma from "@/app/_lib/db";
3 | import { formatDate, getTranslate } from "@/app/_utils/helpers";
4 | import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
5 |
6 | export async function generateMetadata({
7 | params: { locale },
8 | }: {
9 | params: { locale: string };
10 | }) {
11 | const t = await getTranslations({ locale, namespace: "metadata" });
12 |
13 | return {
14 | title: `${t("Orders")}`,
15 | };
16 | }
17 |
18 | export default async function OrdersPage({
19 | params: { locale },
20 | }: {
21 | params: { locale: string };
22 | }) {
23 | unstable_setRequestLocale(locale);
24 |
25 | const orders = await prisma.order.findMany({
26 | select: {
27 | amount: true,
28 | createdAt: true,
29 | status: true,
30 | id: true,
31 | User: {
32 | select: {
33 | firstName: true,
34 | email: true,
35 | profileImage: true,
36 | lastName: true,
37 | },
38 | },
39 | },
40 | orderBy: {
41 | createdAt: "desc",
42 | },
43 | });
44 | const { t } = await getTranslate();
45 | return (
46 |
47 |
48 |
{t("Orders")}
49 |
{t("Orders label")}
50 |
51 | {orders && orders.length > 0 ? (
52 |
53 |
54 |
55 | | {t("Customer")} |
56 | {t("Type")} |
57 | {t("Status")} |
58 | {t("Date")} |
59 | {t("Amount")} |
60 |
61 |
62 |
63 | {orders.map((order) => (
64 |
68 | |
69 |
73 | {order.User?.firstName} {order.User?.lastName}
74 |
75 |
79 | {order.User?.email}
80 |
81 | |
82 | {t("Order")} |
83 | {t(order.status)} |
84 |
85 | {formatDate(order.createdAt)}
86 | |
87 |
88 | ${new Intl.NumberFormat("en-Us").format(order.amount / 100)}
89 | |
90 |
91 | ))}
92 |
93 |
94 | ) : (
95 |
96 | {t("not found orders")}
97 |
98 | )}
99 |
100 |
101 | );
102 | }
103 |
--------------------------------------------------------------------------------
/app/_components/ui/Marquee.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useTranslate } from "@/app/_hooks/useTranslate";
4 | import { newPrice } from "@/app/_utils/helpers";
5 |
6 | import FavButton from "../marketking/FavButton";
7 | import LoginFirst from "../marketking/LoginFirst";
8 | import Button from "./Button";
9 | import MyLink from "./MyLink";
10 | import { toggleFavProduct } from "@/app/_actions/toggleFavProduct";
11 | import toast from "react-hot-toast";
12 | import Marquee from "react-fast-marquee";
13 | import ImagesSlider from "./ImagesSlider";
14 |
15 | type ItemType = {
16 | id: string;
17 | name: string;
18 | price: number;
19 | discount: number;
20 | images: string[];
21 | isFav?: boolean;
22 | };
23 |
24 | type User =
25 | | ({
26 | favoriteProducts: {
27 | productId: string;
28 | }[];
29 | } & {
30 | id: string;
31 | email: string;
32 | password: string;
33 | firstName: string;
34 | lastName: string;
35 | profileImage: string;
36 | createdAt: Date;
37 | })
38 | | null;
39 |
40 | function MarqueeProducts({
41 | items,
42 | user,
43 | }: {
44 | items: ItemType[];
45 | user: User | null;
46 | }) {
47 | const { t } = useTranslate();
48 |
49 | async function favProduct({
50 | user,
51 | productId,
52 | }: {
53 | user: User;
54 | productId: string;
55 | }) {
56 | const res = await toggleFavProduct({ user, productId });
57 |
58 | if (res?.success && res?.favProduct) {
59 | toast.success(t("add fav success", { favProduct: res.favProduct }));
60 | }
61 | if (res?.success === false && res?.favProduct) {
62 | toast.success(t("remove fav success", { favProduct: res.favProduct }));
63 | }
64 | }
65 |
66 | if (items.length < 1)
67 | return (
68 | {t("No Featured Products")}
69 | );
70 | return (
71 |
124 | );
125 | }
126 |
127 | export default MarqueeProducts;
128 |
--------------------------------------------------------------------------------
/app/_components/marketking/EditProfileForm.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { useTranslate } from "@/app/_hooks/useTranslate";
3 | import { UploadButton } from "@/app/_lib/uploadthing";
4 | import { User } from "@prisma/client";
5 | import Image from "next/image";
6 | import { useState } from "react";
7 | import toast from "react-hot-toast";
8 | import SubmitButton from "../ui/SubmitButton";
9 | import { useFormState } from "react-dom";
10 | import { editProfile } from "@/app/_actions/editProfile";
11 | import ErrorMessage from "../ui/ErrorMessage";
12 |
13 | function EditProfileForm({ user }: { user: User }) {
14 | const [image, setImage] = useState(user.profileImage);
15 | const [state, formAction] = useFormState(editProfile, {});
16 |
17 | const { t } = useTranslate();
18 |
19 | return (
20 |
109 | );
110 | }
111 |
112 | export default EditProfileForm;
113 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 | import { withUt } from "uploadthing/tw";
3 |
4 | const config = {
5 | darkMode: ["class"],
6 | content: [
7 | "./pages/**/*.{ts,tsx}",
8 | "./components/**/*.{ts,tsx}",
9 | "./app/**/*.{ts,tsx}",
10 | "./src/**/*.{ts,tsx}",
11 | ],
12 | prefix: "",
13 | theme: {
14 | container: {
15 | center: true,
16 | padding: "2rem",
17 | screens: {
18 | "2xl": "1400px",
19 | },
20 | },
21 | screens: {
22 | sm: "640px",
23 | md: "895px",
24 | lg: "1024px",
25 | xl: "1280px",
26 | "2xl": "1536px",
27 | },
28 | extend: {
29 | colors: {
30 | "main-background": "var(--main-background)",
31 | "sec-background": "var(--second-background)",
32 | "third-background": "var(--third-background)",
33 | "main-text": "var(--main-text)",
34 | "second-text": "var(--second-text)",
35 | "primary-color": "var(--primary-color)",
36 | "primary-bg-color": "var(--primary-bg-color)",
37 | "primary-color-hover": "var(--primary-color-hover)",
38 | "primary--color-hover": "var(--primary-color-hover)",
39 | "hover-button": "var(--hover-button)",
40 | error: "var(--error)",
41 | border: "hsl(var(--border))",
42 | input: "hsl(var(--input))",
43 | ring: "hsl(var(--ring))",
44 | background: "hsl(var(--background))",
45 | foreground: "hsl(var(--foreground))",
46 | primary: {
47 | DEFAULT: "hsl(var(--primary))",
48 | foreground: "hsl(var(--primary-foreground))",
49 | },
50 | secondary: {
51 | DEFAULT: "hsl(var(--secondary))",
52 | foreground: "hsl(var(--secondary-foreground))",
53 | },
54 | destructive: {
55 | DEFAULT: "hsl(var(--destructive))",
56 | foreground: "hsl(var(--destructive-foreground))",
57 | },
58 | muted: {
59 | DEFAULT: "hsl(var(--muted))",
60 | foreground: "hsl(var(--muted-foreground))",
61 | },
62 | accent: {
63 | DEFAULT: "hsl(var(--accent))",
64 | foreground: "hsl(var(--accent-foreground))",
65 | },
66 | popover: {
67 | DEFAULT: "hsl(var(--popover))",
68 | foreground: "hsl(var(--popover-foreground))",
69 | },
70 | card: {
71 | DEFAULT: "hsl(var(--card))",
72 | foreground: "hsl(var(--card-foreground))",
73 | },
74 | },
75 | borderRadius: {
76 | lg: "var(--radius)",
77 | md: "calc(var(--radius) - 2px)",
78 | sm: "calc(var(--radius) - 4px)",
79 | },
80 | keyframes: {
81 | "accordion-down": {
82 | from: { height: "0" },
83 | to: { height: "var(--radix-accordion-content-height)" },
84 | },
85 | "accordion-up": {
86 | from: { height: "var(--radix-accordion-content-height)" },
87 | to: { height: "0" },
88 | },
89 | skeleton: {
90 | "0%": { opacity: "1" },
91 | "50%": { opacity: "0.4 " },
92 | "100%": { opacity: "1" },
93 | },
94 | rotation: {
95 | from: { transform: "rotate(0deg)" },
96 | to: { transform: "rotate(360deg)" },
97 | },
98 | smooth: {
99 | from: { opacity: "0", transform: "translateY(-20px)" },
100 | to: { opacity: "1", transform: "translateY(0px)" },
101 | },
102 | },
103 | animation: {
104 | "accordion-down": "accordion-down 0.2s ease-out",
105 | "accordion-up": "accordion-up 0.2s ease-out",
106 | skeleton: "skeleton 1.5s ease-in-out infinite",
107 | smooth: "smooth 1s ease-in-out ",
108 | rotation: "rotation 2s linear infinite ",
109 | },
110 | },
111 | },
112 | corePlugins: {
113 | aspectRatio: false,
114 | },
115 | } satisfies Config;
116 |
117 | export default withUt(config);
118 |
--------------------------------------------------------------------------------
/app/_components/dashboard/BannerTable.tsx:
--------------------------------------------------------------------------------
1 | import prisma from "@/app/_lib/db";
2 | import { getTranslate } from "@/app/_utils/helpers";
3 | import {
4 | DropdownMenu,
5 | DropdownMenuContent,
6 | DropdownMenuItem,
7 | DropdownMenuLabel,
8 | DropdownMenuSeparator,
9 | DropdownMenuTrigger,
10 | } from "@/components/ui/dropdown-menu";
11 | import Image from "next/image";
12 | import { BsThreeDotsVertical } from "react-icons/bs";
13 | import IconButton from "../ui/IconButton";
14 | import ModalImage from "../ui/ModalImage";
15 | import MyLink from "../ui/MyLink";
16 |
17 | async function BannerTable() {
18 | const banners = await prisma.banner.findMany({
19 | orderBy: {
20 | createdAt: "desc",
21 | },
22 | });
23 |
24 | const { t, isArabic } = await getTranslate();
25 | return (
26 |
27 | {t("Banners")}
28 | {t("Manage Banners")}
29 | {banners.length <= 0 ? (
30 | {t("No Banner")}
31 | ) : (
32 |
33 |
34 |
35 | | {t("Image")} |
36 | {t("title")} |
37 | {t("Actions")} |
38 |
39 |
40 |
41 | {banners.map((banner) => {
42 | const titleWithCap =
43 | banner.title.charAt(0).toUpperCase() +
44 | banner.title.slice(1).toLowerCase();
45 |
46 | return (
47 |
51 |
54 | }
55 | className="w-[95%] h-[100px] relative"
56 | isInTable={true}
57 | modalId={banner.id}
58 | />
59 | | {t(titleWithCap)} |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
73 | {t("Actions")}
74 |
75 |
76 |
77 |
83 |
86 | {t("Delete")}
87 |
88 |
89 |
90 |
91 | |
92 |
93 | );
94 | })}
95 |
96 |
97 | )}
98 |
99 | );
100 | }
101 |
102 | export default BannerTable;
103 |
--------------------------------------------------------------------------------
/app/_components/dashboard/CreateBanner.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { createBanner } from "@/app/_actions/createBanner";
3 | import { useTranslate } from "@/app/_hooks/useTranslate";
4 | import { UploadButton } from "@/app/_lib/uploadthing";
5 | import Image from "next/image";
6 | import { useEffect, useState } from "react";
7 | import { useFormState } from "react-dom";
8 | import toast from "react-hot-toast";
9 | import { IoIosCloseCircle } from "react-icons/io";
10 | import Button from "../ui/Button";
11 | import ModalImage from "../ui/ModalImage";
12 | import SubmitButton from "../ui/SubmitButton";
13 | import ErrorMessage from "../ui/ErrorMessage";
14 | import { useRouter } from "next/navigation";
15 |
16 | function CreateBanner() {
17 | const [image, setImage] = useState([]);
18 | const [state, formAction] = useFormState(createBanner, {});
19 | const { t, isArabic } = useTranslate();
20 | const router = useRouter();
21 |
22 | useEffect(() => {
23 | if (state?.success) {
24 | toast.success(t("create success banner"));
25 | router.push(isArabic ? "/ar/dashboard/banner" : "/en/dashboard/banner");
26 | } else if (state?.success === false) {
27 | toast.error(t("create failed banner"));
28 | }
29 | }, [state?.success, t, router, isArabic]);
30 |
31 | const handleDeleteImage = (index: number) => {
32 | setImage(image.filter((_: any, i) => i !== index));
33 | };
34 | return (
35 |
112 | );
113 | }
114 |
115 | export default CreateBanner;
116 |
--------------------------------------------------------------------------------
/app/[locale]/(marketking)/customer-order/page.tsx:
--------------------------------------------------------------------------------
1 | import Button from "@/app/_components/ui/Button";
2 | import IconButton from "@/app/_components/ui/IconButton";
3 | import MyLink from "@/app/_components/ui/MyLink";
4 | import PrintButton from "@/app/_components/ui/PrintButton";
5 | import prisma from "@/app/_lib/db";
6 | import { getUser } from "@/app/_utils/getUser";
7 | import { getTranslate } from "@/app/_utils/helpers";
8 | import { getTranslations, unstable_setRequestLocale } from "next-intl/server";
9 | import { IoMdArrowRoundBack, IoMdArrowRoundForward } from "react-icons/io";
10 | import { PiCurrencyCircleDollarFill } from "react-icons/pi";
11 |
12 | export async function generateMetadata({
13 | params: { locale },
14 | }: {
15 | params: { locale: string };
16 | }) {
17 | const t = await getTranslations({ locale, namespace: "metadata" });
18 |
19 | return {
20 | title: `${t("Customer Order")}`,
21 | };
22 | }
23 |
24 | async function CustomerOrderPage({
25 | params: { locale },
26 | }: {
27 | params: { locale: string };
28 | }) {
29 | unstable_setRequestLocale(locale);
30 | const user = await getUser();
31 |
32 | const customerOrder = await prisma.order.findMany({
33 | where: { userId: user?.id ?? "" },
34 | });
35 |
36 | const { isArabic, t } = await getTranslate();
37 | return (
38 |
39 |
40 |
41 |
42 | {isArabic ? : }
43 |
44 |
45 |
{t("Customer Order")}
46 |
47 |
48 | {customerOrder && customerOrder.length > 0 ? (
49 | <>
50 |
51 | {customerOrder.map((order) => (
52 | -
56 |
57 |
58 |
59 |
60 |
61 | {" "}
62 | {t("Bill")}#{order.amount}
63 |
64 |
65 |
66 |
67 | {t(order.status)}
68 |
69 |
70 |
71 |
72 |
{t("Date")}
73 |
74 | {Intl.DateTimeFormat(
75 | isArabic ? "ar-EG" : "en-US",
76 | {}
77 | ).format(order.createdAt)}
78 |
79 |
{" "}
80 |
81 |
{t("Total Price")}
82 |
83 | {new Intl.NumberFormat("en-Us").format(
84 | order.amount / 100
85 | )}
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
{t("Status")}
95 |
{t(order.status)}
96 |
{" "}
97 |
98 |
{t("Reference")}
99 |
{order.id.slice(0, 5)}
100 |
101 |
102 |
103 |
104 | ))}
105 |
106 |
107 | >
108 | ) : (
109 |
110 |
{t("No Orders Found")}
111 |
115 | {t("Shop Now")}{" "}
116 | {isArabic ? "←" : "→"}
117 |
118 |
119 | )}
120 |
121 | );
122 | }
123 |
124 | export default CustomerOrderPage;
125 |
--------------------------------------------------------------------------------
/app/_components/marketking/WhishList.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import IconButton from "../ui/IconButton";
3 | import { FaHeart } from "react-icons/fa";
4 | import { IUserIncludeFavorites } from "@/app/_utils/types";
5 | import {
6 | DropdownMenu,
7 | DropdownMenuContent,
8 | DropdownMenuItem,
9 | DropdownMenuLabel,
10 | DropdownMenuSeparator,
11 | DropdownMenuTrigger,
12 | } from "@/components/ui/dropdown-menu";
13 | import { useTranslate } from "@/app/_hooks/useTranslate";
14 | import MyLink from "../ui/MyLink";
15 | import Image from "next/image";
16 | import Button from "../ui/Button";
17 | import { newPrice, oldPrice } from "@/app/_utils/helpers";
18 |
19 | import { FaHeartBroken } from "react-icons/fa";
20 |
21 | async function WhishList({ user }: { user: IUserIncludeFavorites }) {
22 | const whishCount = user?.favoriteProducts.length || 0;
23 | const { t, isArabic } = useTranslate();
24 | return (
25 |
26 |
27 |
28 |
29 | {whishCount > 0 && (
30 |
31 | {whishCount}
32 |
33 | )}
34 |
35 |
36 |
40 |
41 | {t("WhishList")}
42 |
43 |
44 |
45 | {user?.favoriteProducts?.length ? (
46 | <>
47 |
48 | {user.favoriteProducts.map((fav) => (
49 | -
50 |
56 |
60 |
61 |
67 |
68 |
72 | {fav.product.name}
73 |
74 |
75 | ${newPrice(fav.product.price, fav.product.discount)}
76 | {oldPrice(fav.product.price, fav.product.discount) && (
77 |
78 | ($
79 | {oldPrice(fav.product.price, fav.product.discount)})
80 |
81 | )}
82 | {fav.product.isFeatured && (
83 |
84 | {t("Featured")}
85 |
86 | )}
87 |
88 |
89 |
90 |
91 | ))}
92 |
93 |
97 |
102 |
103 | >
104 | ) : (
105 | <>
106 |
107 | {t("no favorite")}
108 | >
109 | )}
110 |
111 |
112 | );
113 | }
114 |
115 | export default WhishList;
116 |
--------------------------------------------------------------------------------