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;
score: Score;
plan: "base" | "premium";
enableAiChat?: boolean;
bgType?: "pattern" | "custom" | "color";
bgBlur?: number;
bgOpacity?: number;
+1 -1
View File
@@ -161,7 +161,7 @@ function AiPage() {
key={labelKey}
type="button"
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")}
>
<Icon size={15} className="stroke-foreground" variant="Bold" />
+15 -61
View File
@@ -51,9 +51,7 @@ const MenuItem = ({ food }: MenuItemProps) => {
if (!content) return "";
if (Array.isArray(content)) {
return content
.filter((item) => item && typeof item === "string")
.join("، ");
return content.filter((item) => item && typeof item === "string").join("، ");
}
return "";
@@ -74,15 +72,9 @@ const MenuItem = ({ food }: MenuItemProps) => {
return basePrice;
}, [food.price, food.discount]);
const formattedPrice = useMemo(
() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"),
[finalPrice],
);
const formattedPrice = useMemo(() => (finalPrice ? finalPrice.toLocaleString("fa-IR") : "0"), [finalPrice]);
const formattedOriginalPrice = useMemo(
() => (food.price ? food.price.toLocaleString("fa-IR") : "0"),
[food.price],
);
const formattedOriginalPrice = useMemo(() => (food.price ? food.price.toLocaleString("fa-IR") : "0"), [food.price]);
const hasDiscount = useMemo(() => (food.discount || 0) > 0, [food.discount]);
@@ -102,63 +94,34 @@ const MenuItem = ({ food }: MenuItemProps) => {
return (
<div className="flex gap-4 w-full h-full items-center">
<Link href={foodDetailUrl} className="cursor-pointer">
<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)}
/>
<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)} />
</Link>
<div className="w-full inline-flex flex-col justify-between min-w-0">
<Link href={foodDetailUrl} className="cursor-pointer min-w-0">
<div className="min-w-0">
<div className="text-sm2 font-normal text-black dark:text-white wrap-break-word">
{foodName}
</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 className="text-sm2 font-normal text-black dark:text-white wrap-break-word">{foodName}</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>
</Link>
<div className="inline-flex mt-2 gap-2 justify-between w-full items-start">
<div
className="w-full flex flex-col gap-1 items-end"
dir="ltr"
>
<div className="w-full flex flex-col gap-1 items-end" dir="ltr">
{hasDiscount ? (
<>
<span className="text-xs text-disabled-text line-through">
{formattedOriginalPrice} T
</span>
<span className="text-sm font-medium text-black dark:text-white">
{formattedPrice} T
</span>
<span className="text-xs text-disabled-text line-through">{formattedOriginalPrice} T</span>
<span className="text-sm font-medium text-black dark:text-white">{formattedPrice} T</span>
</>
) : (
<span className="text-sm text-right">
{formattedPrice} T
</span>
<span className="text-sm text-right">{formattedPrice} T</span>
)}
</div>
<motion.div
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 ? (
<button
onClick={handleAddToCart}
disabled={!isAvailable}
className="inline-flex w-full justify-center items-center gap-2 disabled:pointer-events-none"
>
<button onClick={handleAddToCart} disabled={!isAvailable} className="inline-flex w-full justify-center items-center gap-2 disabled:pointer-events-none">
{isAvailable && <PlusIcon />}
<div className="text-sm2 pt-0.5 font-normal text-foreground">
{isAvailable ? "افزودن" : "عدم موجودی"}
</div>
<div className="text-sm2 pt-0.5 font-normal text-foreground">{isAvailable ? "افزودن" : "عدم موجودی"}</div>
</button>
) : (
<>
@@ -169,19 +132,10 @@ const MenuItem = ({ food }: MenuItemProps) => {
>
<PlusIcon className="text-foreground" />
</button>
<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"
>
<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">
{quantity}
</motion.div>
<button
onClick={handleRemoveFromCart}
className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors"
>
<button onClick={handleRemoveFromCart} className="bg-container hover:bg-container/60 active:bg-container/30 rounded-sm p-2 transition-colors">
<MinusIcon className="text-foreground" />
</button>
</>
+5 -2
View File
@@ -44,6 +44,7 @@ type MenuItemType = {
href: string | undefined;
title: string;
icon: Icon;
badge?: string;
auth?: boolean;
guestOnly?: boolean;
premiumOnly?: boolean;
@@ -57,7 +58,7 @@ const menuItems: Array<Array<MenuItemType>> = [
{ href: "order/history", title: "Orders", icon: CalendarSearch, premiumOnly: true },
{ href: "transactions", title: "Transactions", icon: Receipt1, premiumOnly: true },
{ 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: "report", title: "ReportProblem", icon: NoteBoardIcon },
{ href: "?installpwa", title: "InstallApp", icon: DirectboxReceive },
@@ -251,17 +252,19 @@ function SideMenu({ menuState, toggleMenuState, nightModeState, togglenightModeS
if (item.guestOnly && isSuccess) return false;
if (item.href === "?installpwa" && isPWAInstalled) return false;
if (item.premiumOnly && !isPremium) return false;
if (item.href === "ai" && !aboutData?.data?.enableAiChat) return false;
return true;
})
.map(({ icon: Icon, ...item }, index) => {
const href = `/${name}/${item.href}`;
const isActive = pathname === href;
return (
<li key={index} className="mt-4 h-6">
<li key={index} className={clsx("mt-4", item.badge ? "h-8" : "h-6")}>
<SideMenuItem
className={clsx(isActive && "text-primary! border-primary! dark:text-neutral-200! dark:border-neutral-200!", "border-r-4!")}
href={href ?? ""}
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}
icon={
<Icon
+26 -19
View File
@@ -1,29 +1,36 @@
'use client';
"use client";
import Link, { LinkProps } from 'next/link'
import React from 'react'
import Link, { LinkProps } from "next/link";
import React from "react";
type Props = {
icon: React.ReactElement
title: string
} & LinkProps & React.AnchorHTMLAttributes<HTMLAnchorElement>
icon: React.ReactElement;
title: string;
badge?: string;
} & LinkProps &
React.AnchorHTMLAttributes<HTMLAnchorElement>;
export default function SideMenuItem({ href, title, icon, className, children, ...restProps }: Props) {
return (
<Link
{...restProps}
href={href}
className={`
export default function SideMenuItem({ href, title, icon, badge, className, children, ...restProps }: Props) {
return (
<Link
{...restProps}
href={href}
className={`
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
data-active:border-s-6 data-active:border-primary data-active:text-primary
dark:data-active:border-foreground dark:data-active:text-foreground
${className}
`}
>
{children}
<span className="shrink-0">{icon}</span>
<span className='mt-0.5'>{title}</span>
</Link>
)
}
>
{children}
<span className="shrink-0">{icon}</span>
<span className="flex relative flex-col items-start gap-0.5">
{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": "لیست تراکنش ها",
"Games": "بازی و سرگرمی ها",
"AI": "چی بخورم؟",
"AIBadge": "هوش مصنوعی",
"ShareWithFriends": "معرفی به دوستان",
"ReportProblem": "گزارش اشکال",
"InstallApp": "نصب اپلیکیشن",