Compare commits

...

9 Commits

Author SHA1 Message Date
hamid zarghami 83c8c3a121 box ai
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-29 12:00:53 +03:30
hamid zarghami 3d2de6fd4b problem solved
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-29 09:41:41 +03:30
hamid zarghami 2248845467 show ai link in header
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-29 09:41:09 +03:30
hamid zarghami b19c1cd3ca navigate back in ai [age
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-28 09:32:30 +03:30
hamid zarghami 2465ae1020 fix
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 15:50:30 +03:30
hamid zarghami 0cb119629c badge and show ai button
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 14:40:33 +03:30
hamid zarghami da42e42b7c base url
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 11:39:05 +03:30
hamid zarghami 6849062f28 offer food
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-26 10:37:59 +03:30
hamid zarghami 3409b8114e base url
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled
2026-07-23 11:16:24 +03:30
10 changed files with 227 additions and 120 deletions
+84
View File
@@ -0,0 +1,84 @@
<svg width="62" height="62" viewBox="0 0 62 62" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M29.837 7.64759C29.837 11.8811 29.9738 16.1145 29.837 20.348C29.7001 23.489 27.6471 25.8106 25.1835 27.7225C24.7729 27.9956 24.3623 28.2687 23.678 28.815C24.9098 29.6344 25.8678 30.3172 26.9628 31.1366H0C0.4106 31 0.684334 30.8634 0.821201 30.8634C4.79034 30.0441 9.03321 29.7709 12.8655 28.1322C21.8987 24.3084 27.5102 17.6168 29.7001 8.05728L29.837 7.64759Z" fill="url(#paint0_linear_13943_20143)"/>
<path d="M33.6692 23.3524C33.2586 23.8987 32.7112 24.4449 32.4374 24.8546C32.0268 25.4009 31.89 25.9471 31.4794 26.63V0C31.4794 0 31.4794 0 31.6162 0C32.0268 2.04846 32.3006 3.96035 32.7112 6.00881C34.4904 16.3877 40.2388 23.7621 49.9564 27.859C51.5988 28.5419 53.5149 28.9515 55.2942 29.6344C55.1573 29.6344 55.0204 29.6344 54.8836 29.6344C51.4619 29.6344 48.1771 29.6344 44.7554 29.6344C40.2388 29.7709 36.954 27.7225 34.3536 24.0352C34.3536 23.8987 34.0798 23.6256 33.943 23.489C33.943 23.489 33.943 23.489 33.6692 23.3524Z" fill="url(#paint1_linear_13943_20143)"/>
<path d="M8.48601 32.3656C12.1814 32.3656 16.0137 32.3656 19.7091 32.3656C23.2676 32.3656 25.8681 34.4141 27.9211 37.1454C28.1948 37.5551 28.4686 37.9648 29.016 38.6476C29.8372 37.5551 30.5216 36.4626 31.3428 35.5066V62C30.9322 59.815 30.6584 57.7665 30.2478 55.5815C28.058 43.5639 21.0778 35.9163 9.17034 32.6388C9.03347 32.6388 8.75974 32.6388 8.62288 32.5022C8.62288 32.5022 8.62288 32.5022 8.62288 32.3656H8.48601Z" fill="url(#paint2_linear_13943_20143)"/>
<path d="M32.8474 53.8062C32.8474 49.8458 32.7105 45.8855 32.8474 41.9251C32.9842 38.6476 34.9004 36.1894 37.5008 34.2775C37.9114 34.0044 38.322 33.7313 39.0064 33.185C37.9114 32.3656 36.8165 31.6828 35.7216 31H62C60.2207 31.2731 58.5783 31.6828 56.7991 31.9559C50.3663 32.9119 44.7548 35.5066 40.2382 40.1498C36.6796 43.7004 34.3529 47.9339 33.1211 52.8502C33.1211 53.1234 32.9842 53.3965 32.8474 53.8062Z" fill="url(#paint3_linear_13943_20143)"/>
<path d="M29.837 7.64759C29.837 11.8811 29.9738 16.1145 29.837 20.348C29.7001 23.489 27.6471 25.8106 25.1835 27.7225C24.7729 27.9956 24.3623 28.2687 23.678 28.815C24.9098 29.6344 25.8678 30.3172 26.9628 31.1366H0C0.4106 31 0.684334 30.8634 0.821201 30.8634C4.79034 30.0441 9.03321 29.7709 12.8655 28.1322C21.8987 24.3084 27.5102 17.6168 29.7001 8.05728L29.837 7.64759Z" fill="url(#paint4_linear_13943_20143)"/>
<path d="M33.6692 23.3524C33.2586 23.8987 32.7112 24.4449 32.4374 24.8546C32.0268 25.4009 31.89 25.9471 31.4794 26.63V0C31.4794 0 31.4794 0 31.6162 0C32.0268 2.04846 32.3006 3.96035 32.7112 6.00881C34.4904 16.3877 40.2388 23.7621 49.9564 27.859C51.5988 28.5419 53.5149 28.9515 55.2942 29.6344C55.1573 29.6344 55.0204 29.6344 54.8836 29.6344C51.4619 29.6344 48.1771 29.6344 44.7554 29.6344C40.2388 29.7709 36.954 27.7225 34.3536 24.0352C34.3536 23.8987 34.0798 23.6256 33.943 23.489C33.943 23.489 33.943 23.489 33.6692 23.3524Z" fill="url(#paint5_linear_13943_20143)"/>
<path d="M8.48601 32.3656C12.1814 32.3656 16.0137 32.3656 19.7091 32.3656C23.2676 32.3656 25.8681 34.4141 27.9211 37.1454C28.1948 37.5551 28.4686 37.9648 29.016 38.6476C29.8372 37.5551 30.5216 36.4626 31.3428 35.5066V62C30.9322 59.815 30.6584 57.7665 30.2478 55.5815C28.058 43.5639 21.0778 35.9163 9.17034 32.6388C9.03347 32.6388 8.75974 32.6388 8.62288 32.5022C8.62288 32.5022 8.62288 32.5022 8.62288 32.3656H8.48601Z" fill="url(#paint6_linear_13943_20143)"/>
<path d="M32.8474 53.8062C32.8474 49.8458 32.7105 45.8855 32.8474 41.9251C32.9842 38.6476 34.9004 36.1894 37.5008 34.2775C37.9114 34.0044 38.322 33.7313 39.0064 33.185C37.9114 32.3656 36.8165 31.6828 35.7216 31H62C60.2207 31.2731 58.5783 31.6828 56.7991 31.9559C50.3663 32.9119 44.7548 35.5066 40.2382 40.1498C36.6796 43.7004 34.3529 47.9339 33.1211 52.8502C33.1211 53.1234 32.9842 53.3965 32.8474 53.8062Z" fill="url(#paint7_linear_13943_20143)"/>
<path d="M29.837 7.64759C29.837 11.8811 29.9738 16.1145 29.837 20.348C29.7001 23.489 27.6471 25.8106 25.1835 27.7225C24.7729 27.9956 24.3623 28.2687 23.678 28.815C24.9098 29.6344 25.8678 30.3172 26.9628 31.1366H0C0.4106 31 0.684334 30.8634 0.821201 30.8634C4.79034 30.0441 9.03321 29.7709 12.8655 28.1322C21.8987 24.3084 27.5102 17.6168 29.7001 8.05728L29.837 7.64759Z" fill="url(#paint8_linear_13943_20143)"/>
<path d="M33.6692 23.3524C33.2586 23.8987 32.7112 24.4449 32.4374 24.8546C32.0268 25.4009 31.89 25.9471 31.4794 26.63V0C31.4794 0 31.4794 0 31.6162 0C32.0268 2.04846 32.3006 3.96035 32.7112 6.00881C34.4904 16.3877 40.2388 23.7621 49.9564 27.859C51.5988 28.5419 53.5149 28.9515 55.2942 29.6344C55.1573 29.6344 55.0204 29.6344 54.8836 29.6344C51.4619 29.6344 48.1771 29.6344 44.7554 29.6344C40.2388 29.7709 36.954 27.7225 34.3536 24.0352C34.3536 23.8987 34.0798 23.6256 33.943 23.489C33.943 23.489 33.943 23.489 33.6692 23.3524Z" fill="url(#paint9_linear_13943_20143)"/>
<path d="M8.48601 32.3656C12.1814 32.3656 16.0137 32.3656 19.7091 32.3656C23.2676 32.3656 25.8681 34.4141 27.9211 37.1454C28.1948 37.5551 28.4686 37.9648 29.016 38.6476C29.8372 37.5551 30.5216 36.4626 31.3428 35.5066V62C30.9322 59.815 30.6584 57.7665 30.2478 55.5815C28.058 43.5639 21.0778 35.9163 9.17034 32.6388C9.03347 32.6388 8.75974 32.6388 8.62288 32.5022C8.62288 32.5022 8.62288 32.5022 8.62288 32.3656H8.48601Z" fill="url(#paint10_linear_13943_20143)"/>
<path d="M32.8474 53.8062C32.8474 49.8458 32.7105 45.8855 32.8474 41.9251C32.9842 38.6476 34.9004 36.1894 37.5008 34.2775C37.9114 34.0044 38.322 33.7313 39.0064 33.185C37.9114 32.3656 36.8165 31.6828 35.7216 31H62C60.2207 31.2731 58.5783 31.6828 56.7991 31.9559C50.3663 32.9119 44.7548 35.5066 40.2382 40.1498C36.6796 43.7004 34.3529 47.9339 33.1211 52.8502C33.1211 53.1234 32.9842 53.3965 32.8474 53.8062Z" fill="url(#paint11_linear_13943_20143)"/>
<path d="M29.837 7.64759C29.837 11.8811 29.9738 16.1145 29.837 20.348C29.7001 23.489 27.6471 25.8106 25.1835 27.7225C24.7729 27.9956 24.3623 28.2687 23.678 28.815C24.9098 29.6344 25.8678 30.3172 26.9628 31.1366H0C0.4106 31 0.684334 30.8634 0.821201 30.8634C4.79034 30.0441 9.03321 29.7709 12.8655 28.1322C21.8987 24.3084 27.5102 17.6168 29.7001 8.05728L29.837 7.64759Z" fill="url(#paint12_linear_13943_20143)"/>
<path d="M33.6692 23.3524C33.2586 23.8987 32.7112 24.4449 32.4374 24.8546C32.0268 25.4009 31.89 25.9471 31.4794 26.63V0C31.4794 0 31.4794 0 31.6162 0C32.0268 2.04846 32.3006 3.96035 32.7112 6.00881C34.4904 16.3877 40.2388 23.7621 49.9564 27.859C51.5988 28.5419 53.5149 28.9515 55.2942 29.6344C55.1573 29.6344 55.0204 29.6344 54.8836 29.6344C51.4619 29.6344 48.1771 29.6344 44.7554 29.6344C40.2388 29.7709 36.954 27.7225 34.3536 24.0352C34.3536 23.8987 34.0798 23.6256 33.943 23.489C33.943 23.489 33.943 23.489 33.6692 23.3524Z" fill="url(#paint13_linear_13943_20143)"/>
<path d="M8.48601 32.3656C12.1814 32.3656 16.0137 32.3656 19.7091 32.3656C23.2676 32.3656 25.8681 34.4141 27.9211 37.1454C28.1948 37.5551 28.4686 37.9648 29.016 38.6476C29.8372 37.5551 30.5216 36.4626 31.3428 35.5066V62C30.9322 59.815 30.6584 57.7665 30.2478 55.5815C28.058 43.5639 21.0778 35.9163 9.17034 32.6388C9.03347 32.6388 8.75974 32.6388 8.62288 32.5022C8.62288 32.5022 8.62288 32.5022 8.62288 32.3656H8.48601Z" fill="url(#paint14_linear_13943_20143)"/>
<path d="M32.8474 53.8062C32.8474 49.8458 32.7105 45.8855 32.8474 41.9251C32.9842 38.6476 34.9004 36.1894 37.5008 34.2775C37.9114 34.0044 38.322 33.7313 39.0064 33.185C37.9114 32.3656 36.8165 31.6828 35.7216 31H62C60.2207 31.2731 58.5783 31.6828 56.7991 31.9559C50.3663 32.9119 44.7548 35.5066 40.2382 40.1498C36.6796 43.7004 34.3529 47.9339 33.1211 52.8502C33.1211 53.1234 32.9842 53.3965 32.8474 53.8062Z" fill="url(#paint15_linear_13943_20143)"/>
<defs>
<linearGradient id="paint0_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint1_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint2_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint3_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint4_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint5_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint6_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint7_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint8_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint9_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint10_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint11_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint12_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint13_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint14_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
<linearGradient id="paint15_linear_13943_20143" x1="16.424" y1="6.14538" x2="58.0646" y2="53.3515" gradientUnits="userSpaceOnUse">
<stop stop-color="#B52B9D"/>
<stop offset="1" stop-color="#8393CF"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -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;
+50 -6
View File
@@ -1,21 +1,27 @@
"use client"; "use client";
import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData"; import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
import type { Food } from "@/app/[name]/(Main)/types/Types";
import { useGetProfile } from "@/app/[name]/(Profile)/profile/hooks/userProfileData"; import { useGetProfile } from "@/app/[name]/(Profile)/profile/hooks/userProfileData";
import MenuItem from "@/components/listview/MenuItem";
import MenuItemRenderer from "@/components/listview/MenuItemRenderer";
import { toast } from "@/components/Toast"; import { toast } from "@/components/Toast";
import { extractErrorMessage } from "@/lib/func"; import { extractErrorMessage } from "@/lib/func";
import { glassSurfaceFlat } from "@/lib/styles/glassSurface"; import { glassSurfaceFlat } from "@/lib/styles/glassSurface";
import { Send2 } from "iconsax-react"; import { ArrowLeft, Send2 } from "iconsax-react";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation";
import { FormEvent, useEffect, useRef, useState } from "react"; import { FormEvent, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useChat } from "./hooks/useAiData"; import { useChat } from "./hooks/useAiData";
import { AI_SUGGESTIONS } from "./suggestions"; import { AI_SUGGESTIONS } from "./suggestions";
import type { ChatFood } from "./types/Types";
type Message = { type Message = {
id: string; id: string;
role: "user" | "assistant"; role: "user" | "assistant";
content: string; content: string;
foods?: ChatFood[];
}; };
function getGreetingKey() { function getGreetingKey() {
@@ -26,8 +32,29 @@ function getGreetingKey() {
return "evening"; return "evening";
} }
function toFood(food: ChatFood): Food {
return {
id: food.id,
category: "",
title: food.title,
desc: food.desc,
content: food.content ?? [],
price: food.price,
prepareTime: 0,
isActive: true,
images: food.images ?? [],
inPlaceServe: true,
pickupServe: true,
discount: food.discount ?? 0,
isSpecialOffer: food.isSpecialOffer ?? false,
isAvailable: true,
unavailabilityMessage: null,
};
}
function AiPage() { function AiPage() {
const { t } = useTranslation("common", { keyPrefix: "AI" }); const { t } = useTranslation("common", { keyPrefix: "AI" });
const router = useRouter();
const { data: about } = useGetAbout(); const { data: about } = useGetAbout();
const { data: profile, isSuccess } = useGetProfile(); const { data: profile, isSuccess } = useGetProfile();
const { mutate: chat, isPending } = useChat(); const { mutate: chat, isPending } = useChat();
@@ -62,13 +89,14 @@ function AiPage() {
id: `a-${Date.now()}`, id: `a-${Date.now()}`,
role: "assistant", role: "assistant",
content: res.data.answer, content: res.data.answer,
foods: res.data.foods?.length ? res.data.foods : undefined,
}, },
]); ]);
}, },
onError: (error) => { onError: (error) => {
toast(extractErrorMessage(error, t("ErrorFallback")), "error"); toast(extractErrorMessage(error, t("ErrorFallback")), "error");
}, },
} },
); );
}; };
@@ -79,15 +107,21 @@ function AiPage() {
return ( return (
<section className="flex h-[calc(100svh-6.5rem)] flex-col pt-4 xl:h-[calc(100svh-6rem)]"> <section className="flex h-[calc(100svh-6.5rem)] flex-col pt-4 xl:h-[calc(100svh-6rem)]">
<h1 className="shrink-0 text-center text-sm2 font-medium">{t("Heading")}</h1> <div className="grid shrink-0 grid-cols-3 items-center">
<span />
<h1 className="place-self-center text-sm2 font-medium">{t("Heading")}</h1>
<ArrowLeft className="cursor-pointer place-self-end" size="20" color="currentColor" onClick={() => router.back()} />
</div>
<div className="mt-6 flex min-h-0 flex-1 flex-col"> <div className="mt-6 flex min-h-0 flex-1 flex-col">
{messages.length > 0 ? ( {messages.length > 0 ? (
<ul ref={listRef} className="noscrollbar flex-1 space-y-3 overflow-y-auto pb-4"> <ul ref={listRef} className="noscrollbar flex-1 space-y-3 overflow-y-auto pb-4">
{messages.map((message) => { {messages.map((message) => {
const isUser = message.role === "user"; const isUser = message.role === "user";
const hasFoods = !isUser && !!message.foods?.length;
return ( return (
<li key={message.id} className={`flex ${isUser ? "justify-end" : "justify-start"}`}> <li key={message.id} className={`flex flex-col gap-2 ${isUser ? "items-end" : "items-start"}`}>
<div <div
className={ className={
isUser isUser
@@ -97,6 +131,16 @@ function AiPage() {
> >
<p className="text-xs2 leading-5 whitespace-pre-wrap">{message.content}</p> <p className="text-xs2 leading-5 whitespace-pre-wrap">{message.content}</p>
</div> </div>
{hasFoods && (
<div className="flex w-full flex-col gap-2">
{message.foods!.map((food) => (
<MenuItemRenderer key={food.id}>
<MenuItem food={toFood(food)} />
</MenuItemRenderer>
))}
</div>
)}
</li> </li>
); );
})} })}
@@ -123,7 +167,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" />
@@ -142,7 +186,7 @@ function AiPage() {
placeholder={t("InputPlaceholder")} placeholder={t("InputPlaceholder")}
autoComplete="off" autoComplete="off"
disabled={isPending} disabled={isPending}
className="h-full w-full bg-transparent text-[11px] outline-none placeholder:text-disabled-text disabled:opacity-60" className="h-full w-full bg-transparent text-[11px]! outline-none placeholder:text-disabled-text disabled:opacity-60"
/> />
<button type="submit" disabled={!input.trim() || isPending} className="rounded-full p-1.5 disabled:opacity-40"> <button type="submit" disabled={!input.trim() || isPending} className="rounded-full p-1.5 disabled:opacity-40">
+12
View File
@@ -4,8 +4,20 @@ export type ChatRequest = {
question: string; question: string;
}; };
export type ChatFood = {
id: string;
title: string;
desc: string;
content: string[] | null;
price: number;
images: string[];
discount: number;
isSpecialOffer: boolean;
};
export type ChatData = { export type ChatData = {
answer: string; answer: string;
foods?: ChatFood[];
consumedTokens: number; consumedTokens: number;
cost: number; cost: number;
promptTokens: number; promptTokens: number;
+27 -25
View File
@@ -1,38 +1,40 @@
'use client' "use client";
import React from 'react' import { useGetAbout } from "@/app/[name]/(Main)/about/hooks/useAboutData";
import SearchIcon from '../icons/SearchIcon' import { glassSurfaceFlat } from "@/lib/styles/glassSurface";
import { glassSurfaceFlat } from '@/lib/styles/glassSurface' import Link from "next/link";
import { useParams } from "next/navigation";
import React from "react";
import SearchIcon from "../icons/SearchIcon";
interface SearchboxProps interface SearchboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "results"> {
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'results'> { placeholder?: string;
placeholder?: string onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
} }
export default function SearchBox({ export default function SearchBox({ className, value, placeholder = "", onChange, ...props }: SearchboxProps) {
className, const { name } = useParams<{ name?: string }>();
value, const { data: about } = useGetAbout();
placeholder = '', const showAiLink = !!name && !!about?.data?.enableAiChat;
onChange,
...props
}: SearchboxProps) {
return ( return (
<div <div className="flex gap-4 items-center">
className={glassSurfaceFlat( <div className={glassSurfaceFlat("inline-flex rounded-xl px-4 h-10 w-full items-center content-center", className)}>
'inline-flex rounded-xl px-4 h-10 w-full items-center content-center', <SearchIcon stroke="#8C90A3" />
className,
)}
>
<SearchIcon stroke='#8C90A3' />
<input <input
onChange={onChange} onChange={onChange}
value={value} value={value}
placeholder={placeholder} placeholder={placeholder}
className='text-[#8C90A3] pt-1 block outline-none border-none focus:ring-0 focus:outline-none px-[7px] h-full items-center w-full text-xs!' className="text-[#8C90A3] pt-1 block outline-none border-none focus:ring-0 focus:outline-none px-1.75 h-full items-center w-full text-xs!"
type='search' type="search"
{...props} {...props}
/> />
</div> </div>
) {showAiLink && (
<Link href={`/${name}/ai`} aria-label="AI" className={glassSurfaceFlat("inline-flex items-center justify-center rounded-xl w-10 h-10 shrink-0")}>
<img src={"/assets/images/logo_ai2.svg"} alt="logo_ai" className="w-5" />
</Link>
)}
</div>
);
} }
+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
+15 -9
View File
@@ -1,14 +1,16 @@
'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}
@@ -23,7 +25,11 @@ export default function SideMenuItem({ href, title, icon, className, children, .
> >
{children} {children}
<span className="shrink-0">{icon}</span> <span className="shrink-0">{icon}</span>
<span className='mt-0.5'>{title}</span> <span className="flex items-start gap-2">
<span className="mt-0.5">{title}</span>
{badge && <span className="rounded-md mt-0.5 border border-primary text-primary px-1.5 py-0.5 text-[8px] leading-none font-light dark:bg-white/15 dark:text-neutral-200">{badge}</span>}
</span>
</Link> </Link>
) );
} }
+2 -2
View File
@@ -1,6 +1,6 @@
// export const API_BASE_URL = "https://dmenuplus-api-production.dev.danakcorp.com"; // export const API_BASE_URL = "https://dmenuplus-api-production.dev.danakcorp.com";
export const API_BASE_URL = "http://192.168.99.131:2000"; // export const API_BASE_URL = "http://10.202.59.88:2000";
// export const API_BASE_URL = "https://dmenu-api.danakcorp.com"; export const API_BASE_URL = "https://dmenu-api.danakcorp.com";
process.env.NEXT_PUBLIC_API_URL ?? (process.env.NODE_ENV === "development" ? "http://192.168.99.131:2000" : "https://dmenu-api.danakcorp.com"); process.env.NEXT_PUBLIC_API_URL ?? (process.env.NODE_ENV === "development" ? "http://192.168.99.131:2000" : "https://dmenu-api.danakcorp.com");
export const TOKEN_NAME = "dmenu-t"; export const TOKEN_NAME = "dmenu-t";
export const REFRESH_TOKEN_NAME = "dmenu-rt"; export const REFRESH_TOKEN_NAME = "dmenu-rt";
+1
View File
@@ -16,6 +16,7 @@
"Transactions": "لیست تراکنش ها", "Transactions": "لیست تراکنش ها",
"Games": "بازی و سرگرمی ها", "Games": "بازی و سرگرمی ها",
"AI": "چی بخورم؟", "AI": "چی بخورم؟",
"AIBadge": "هوش مصنوعی",
"ShareWithFriends": "معرفی به دوستان", "ShareWithFriends": "معرفی به دوستان",
"ReportProblem": "گزارش اشکال", "ReportProblem": "گزارش اشکال",
"InstallApp": "نصب اپلیکیشن", "InstallApp": "نصب اپلیکیشن",