badge and show ai button
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"Transactions": "لیست تراکنش ها",
|
"Transactions": "لیست تراکنش ها",
|
||||||
"Games": "بازی و سرگرمی ها",
|
"Games": "بازی و سرگرمی ها",
|
||||||
"AI": "چی بخورم؟",
|
"AI": "چی بخورم؟",
|
||||||
|
"AIBadge": "هوش مصنوعی",
|
||||||
"ShareWithFriends": "معرفی به دوستان",
|
"ShareWithFriends": "معرفی به دوستان",
|
||||||
"ReportProblem": "گزارش اشکال",
|
"ReportProblem": "گزارش اشکال",
|
||||||
"InstallApp": "نصب اپلیکیشن",
|
"InstallApp": "نصب اپلیکیشن",
|
||||||
|
|||||||
Reference in New Issue
Block a user