badge and show ai button
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-07-26 14:40:33 +03:30
parent da42e42b7c
commit 0cb119629c
6 changed files with 49 additions and 83 deletions
@@ -46,6 +46,7 @@ export interface Restaurant {
domain: string; domain: string;
score: Score; score: Score;
plan: "base" | "premium"; plan: "base" | "premium";
enableAiChat?: boolean;
bgType?: "pattern" | "custom" | "color"; bgType?: "pattern" | "custom" | "color";
bgBlur?: number; bgBlur?: number;
bgOpacity?: number; bgOpacity?: number;
+1 -1
View File
@@ -161,7 +161,7 @@ function AiPage() {
key={labelKey} key={labelKey}
type="button" type="button"
disabled={isPending} disabled={isPending}
onClick={() => setInput(t(labelKey))} onClick={() => sendMessage(t(labelKey))}
className={glassSurfaceFlat("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 transition-[filter] active:brightness-95 disabled:opacity-50")} className={glassSurfaceFlat("inline-flex items-center gap-1.5 rounded-lg px-3 py-1.5 transition-[filter] active:brightness-95 disabled:opacity-50")}
> >
<Icon size={15} className="stroke-foreground" variant="Bold" /> <Icon size={15} className="stroke-foreground" variant="Bold" />
+15 -61
View File
@@ -51,9 +51,7 @@ const MenuItem = ({ food }: MenuItemProps) => {
if (!content) return ""; if (!content) return "";
if (Array.isArray(content)) { if (Array.isArray(content)) {
return content return content.filter((item) => item && typeof item === "string").join("، ");
.filter((item) => item && typeof item === "string")
.join("، ");
} }
return ""; return "";
@@ -74,15 +72,9 @@ const MenuItem = ({ food }: MenuItemProps) => {
return basePrice; return basePrice;
}, [food.price, food.discount]); }, [food.price, food.discount]);
const formattedPrice = useMemo( const formattedPrice = useMemo(() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"), [finalPrice]);
() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"),
[finalPrice],
);
const formattedOriginalPrice = useMemo( const formattedOriginalPrice = useMemo(() => (food.price ? food.price.toLocaleString("fa-IR") : "0"), [food.price]);
() => (food.price ? food.price.toLocaleString("fa-IR") : "0"),
[food.price],
);
const hasDiscount = useMemo(() => (food.discount || 0) > 0, [food.discount]); const hasDiscount = useMemo(() => (food.discount || 0) > 0, [food.discount]);
@@ -102,63 +94,34 @@ const MenuItem = ({ food }: MenuItemProps) => {
return ( return (
<div className="flex gap-4 w-full h-full items-center"> <div className="flex gap-4 w-full h-full items-center">
<Link href={foodDetailUrl} className="cursor-pointer"> <Link href={foodDetailUrl} className="cursor-pointer">
<img <img className="rounded-xl bg-[#F2F2F9] min-w-28 w-28 max-h-28 object-cover" src={imageSrc} height={112} width={112} alt={foodName} loading="lazy" onError={() => setImageSrc(fallbackImage)} />
className="rounded-xl bg-[#F2F2F9] min-w-28 w-28 max-h-28 object-cover"
src={imageSrc}
height={112}
width={112}
alt={foodName}
loading="lazy"
onError={() => setImageSrc(fallbackImage)}
/>
</Link> </Link>
<div className="w-full inline-flex flex-col justify-between min-w-0"> <div className="w-full inline-flex flex-col justify-between min-w-0">
<Link href={foodDetailUrl} className="cursor-pointer min-w-0"> <Link href={foodDetailUrl} className="cursor-pointer min-w-0">
<div className="min-w-0"> <div className="min-w-0">
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word"> <div className="text-sm2 font-normal text-black dark:text-white wrap-break-word">{foodName}</div>
{foodName} {(foodContent || foodDescription) && <div className="text-[#7F7F7F] line-clamp-2 text-xs leading-5 font-normal mt-2 wrap-break-word overflow-hidden">{foodContent || foodDescription}</div>}
</div>
{(foodContent || foodDescription) && (
<div className="text-[#7F7F7F] line-clamp-2 text-xs leading-5 font-normal mt-2 wrap-break-word overflow-hidden">
{foodContent || foodDescription}
</div>
)}
</div> </div>
</Link> </Link>
<div className="inline-flex mt-2 gap-2 justify-between w-full items-start"> <div className="inline-flex mt-2 gap-2 justify-between w-full items-start">
<div <div className="w-full flex flex-col gap-1 items-end" dir="ltr">
className="w-full flex flex-col gap-1 items-end"
dir="ltr"
>
{hasDiscount ? ( {hasDiscount ? (
<> <>
<span className="text-xs text-disabled-text line-through"> <span className="text-xs text-disabled-text line-through">{formattedOriginalPrice} T</span>
{formattedOriginalPrice} T <span className="text-sm font-medium text-black dark:text-white">{formattedPrice} T</span>
</span>
<span className="text-sm font-medium text-black dark:text-white">
{formattedPrice} T
</span>
</> </>
) : ( ) : (
<span className="text-sm text-right"> <span className="text-sm text-right">{formattedPrice} T</span>
{formattedPrice} T
</span>
)} )}
</div> </div>
<motion.div <motion.div
whileTap={isAvailable ? { scale: 1.05 } : undefined} whileTap={isAvailable ? { scale: 1.05 } : undefined}
className="bg-background pattern-secondary-bg active:drop-shadow-xs max-w-[115px] rounded-md w-full h-8 inline-flex p-1 justify-between items-center gap-2 relative overflow-hidden" className="bg-background pattern-secondary-bg active:drop-shadow-xs max-w-28.75 rounded-md w-full h-8 inline-flex p-1 justify-between items-center gap-2 relative overflow-hidden"
> >
{quantity <= 0 ? ( {quantity <= 0 ? (
<button <button onClick={handleAddToCart} disabled={!isAvailable} className="inline-flex w-full justify-center items-center gap-2 disabled:pointer-events-none">
onClick={handleAddToCart}
disabled={!isAvailable}
className="inline-flex w-full justify-center items-center gap-2 disabled:pointer-events-none"
>
{isAvailable && <PlusIcon />} {isAvailable && <PlusIcon />}
<div className="text-sm2 pt-0.5 font-normal text-foreground"> <div className="text-sm2 pt-0.5 font-normal text-foreground">{isAvailable ? "افزودن" : "عدم موجودی"}</div>
{isAvailable ? "افزودن" : "عدم موجودی"}
</div>
</button> </button>
) : ( ) : (
<> <>
@@ -169,19 +132,10 @@ const MenuItem = ({ food }: MenuItemProps) => {
> >
<PlusIcon className="text-foreground" /> <PlusIcon className="text-foreground" />
</button> </button>
<motion.div <motion.div key={quantity} initial={{ scale: 1 }} animate={{ scale: [1.2, 0.95, 1] }} transition={{ duration: 0.3 }} className="text-sm2 pt-0.5 font-semibold">
key={quantity}
initial={{ scale: 1 }}
animate={{ scale: [1.2, 0.95, 1] }}
transition={{ duration: 0.3 }}
className="text-sm2 pt-0.5 font-semibold"
>
{quantity} {quantity}
</motion.div> </motion.div>
<button <button onClick={handleRemoveFromCart} className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors">
onClick={handleRemoveFromCart}
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
>
<MinusIcon className="text-foreground" /> <MinusIcon className="text-foreground" />
</button> </button>
</> </>
+5 -2
View File
@@ -44,6 +44,7 @@ type MenuItemType = {
href: string | undefined; href: string | undefined;
title: string; title: string;
icon: Icon; icon: Icon;
badge?: string;
auth?: boolean; auth?: boolean;
guestOnly?: boolean; guestOnly?: boolean;
premiumOnly?: boolean; premiumOnly?: boolean;
@@ -57,7 +58,7 @@ const menuItems: Array<Array<MenuItemType>> = [
{ href: "order/history", title: "Orders", icon: CalendarSearch, premiumOnly: true }, { href: "order/history", title: "Orders", icon: CalendarSearch, premiumOnly: true },
{ href: "transactions", title: "Transactions", icon: Receipt1, premiumOnly: true }, { href: "transactions", title: "Transactions", icon: Receipt1, premiumOnly: true },
{ href: "game", title: "Games", icon: Game }, { href: "game", title: "Games", icon: Game },
{ href: "ai", title: "AI", icon: MessageProgramming }, { href: "ai", title: "AI", icon: MessageProgramming, badge: "AIBadge" },
{ href: "?share", title: "ShareWithFriends", icon: Like1 }, { href: "?share", title: "ShareWithFriends", icon: Like1 },
{ href: "report", title: "ReportProblem", icon: NoteBoardIcon }, { href: "report", title: "ReportProblem", icon: NoteBoardIcon },
{ href: "?installpwa", title: "InstallApp", icon: DirectboxReceive }, { href: "?installpwa", title: "InstallApp", icon: DirectboxReceive },
@@ -251,17 +252,19 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
if (item.guestOnly && isSuccess) return false; if (item.guestOnly && isSuccess) return false;
if (item.href === "?installpwa" && isPWAInstalled) return false; if (item.href === "?installpwa" && isPWAInstalled) return false;
if (item.premiumOnly && !isPremium) return false; if (item.premiumOnly && !isPremium) return false;
if (item.href === "ai" && !aboutData?.data?.enableAiChat) return false;
return true; return true;
}) })
.map(({ icon: Icon, ...item }, index) => { .map(({ icon: Icon, ...item }, index) => {
const href = `/${name}/${item.href}`; const href = `/${name}/${item.href}`;
const isActive = pathname === href; const isActive = pathname === href;
return ( return (
<li key={index} className="mt-4 h-6"> <li key={index} className={clsx("mt-4", item.badge ? "h-8" : "h-6")}>
<SideMenuItem <SideMenuItem
className={clsx(isActive && "text-primary! border-primary! dark:text-neutral-200! dark:border-neutral-200!", "border-r-4!")} className={clsx(isActive && "text-primary! border-primary! dark:text-neutral-200! dark:border-neutral-200!", "border-r-4!")}
href={href ?? ""} href={href ?? ""}
title={tMenu(item.title)} title={tMenu(item.title)}
badge={item.badge ? tMenu(item.badge) : undefined}
onClick={typeof item.href === "string" ? hrefOnClicks.find((i) => i.href === item.href)?.handler : undefined} onClick={typeof item.href === "string" ? hrefOnClicks.find((i) => i.href === item.href)?.handler : undefined}
icon={ icon={
<Icon <Icon
+26 -19
View File
@@ -1,29 +1,36 @@
'use client'; "use client";
import Link, { LinkProps } from 'next/link' import Link, { LinkProps } from "next/link";
import React from 'react' import React from "react";
type Props = { type Props = {
icon: React.ReactElement icon: React.ReactElement;
title: string title: string;
} & LinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement> badge?: string;
} & LinkProps &
React.AnchorHTMLAttributes<HTMLAnchorElement>;
export default function SideMenuItem({ href, title, icon, className, children, ...restProps }: Props) { export default function SideMenuItem({ href, title, icon, badge, className, children, ...restProps }: Props) {
return ( return (
<Link <Link
{...restProps} {...restProps}
href={href} href={href}
className={` className={`
inline-flex gap-3 ps-6 xl:ps-9 h-full w-full items-center overflow-hidden text-nowrap inline-flex gap-3 ps-6 xl:ps-9 h-full w-full items-center overflow-hidden text-nowrap
text-xs font-light text-disabled-text border-transparent text-xs font-light text-disabled-text border-transparent
data-active:border-s-6 data-active:border-primary data-active:text-primary data-active:border-s-6 data-active:border-primary data-active:text-primary
dark:data-active:border-foreground dark:data-active:text-foreground dark:data-active:border-foreground dark:data-active:text-foreground
${className} ${className}
`} `}
> >
{children} {children}
<span className="shrink-0">{icon}</span> <span className="shrink-0">{icon}</span>
<span className='mt-0.5'>{title}</span> <span className="flex relative flex-col items-start gap-0.5">
</Link> {badge && (
) <span className="rounded-full -top-2 right-9 absolute bg-primary/15 px-1.5 py-0.5 text-[8px] font-medium leading-none text-primary dark:bg-white/15 dark:text-neutral-200">{badge}</span>
} )}
<span className="mt-0.5">{title}</span>
</span>
</Link>
);
}
+1
View File
@@ -16,6 +16,7 @@
"Transactions": "لیست تراکنش ها", "Transactions": "لیست تراکنش ها",
"Games": "بازی و سرگرمی ها", "Games": "بازی و سرگرمی ها",
"AI": "چی بخورم؟", "AI": "چی بخورم؟",
"AIBadge": "هوش مصنوعی",
"ShareWithFriends": "معرفی به دوستان", "ShareWithFriends": "معرفی به دوستان",
"ReportProblem": "گزارش اشکال", "ReportProblem": "گزارش اشکال",
"InstallApp": "نصب اپلیکیشن", "InstallApp": "نصب اپلیکیشن",