load fast
Build and Deploy Docker Images / build_and_deploy (push) Has been cancelled

This commit is contained in:
hamid zarghami
2026-06-06 09:08:38 +03:30
parent ace6a0b288
commit e68543f869
12 changed files with 255 additions and 248 deletions
+6 -6
View File
@@ -8,7 +8,6 @@ import { getToken } from "@/lib/api/func";
import { ef } from "@/lib/helpers/utfNumbers";
import { motion } from "framer-motion";
import { ArrowLeft, Clock, Cup, Heart, TruckTick } from "iconsax-react";
import Image from "next/image";
import { useParams, useRouter } from "next/navigation";
import { useEffect, useMemo, useState } from "react";
import { useGetProfile } from "../../(Profile)/profile/hooks/userProfileData";
@@ -131,13 +130,14 @@ function FoodPage({}: Props) {
return (
<div className="not-lg:max-w-lg not-lg:place-self-center not-lg:w-full not-lg:mb-10 pt-6 not-lg:flex not-lg:flex-col lg:fixed lg:z-10 lg:top-20 xl:top-[5.5rem] lg:bottom-24 lg:left-5 lg:right-4 xl:right-[285px] lg:grid lg:grid-cols-2 lg:min-h-0 lg:overflow-hidden lg:rounded-2xl">
<div className="relative w-full lg:h-full min-h-0 not-lg:bg-container rounded-2xl overflow-hidden lg:rounded-r-none lg:order-1">
<Image
className="lg:object-contain h-auto object-cover bg-[#F2F2F9]"
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
className="lg:object-contain h-auto object-cover bg-[#F2F2F9] w-full"
src={foodImage}
alt={foodName}
width={1000}
height={100}
priority
onError={(event) => {
event.currentTarget.src = "/assets/images/no-image.png";
}}
/>
<div className="absolute top-4 left-0 pe-5.5 ps-7 w-full inline-flex justify-between items-center">
<div className="bg-container whitespace-nowrap rounded-[34px] py-1.5 px-4 w-min text-xs text-disabled2 dark:text-disabled-text font-bold">
@@ -70,7 +70,19 @@ function CategoryImage({
);
}
return <img src={src} width={size} height={size} alt={alt} loading="lazy" />;
return (
// eslint-disable-next-line @next/next/no-img-element
<img
src={src}
width={size}
height={size}
alt={alt}
loading="lazy"
onError={(event) => {
event.currentTarget.src = "/assets/images/food-image.png";
}}
/>
);
}
type Variant = "large" | "small";
@@ -118,6 +118,7 @@ function OrdersIndex() {
width={64}
height={64}
alt={foodName}
unoptimized
/>
</div>
<div className='flex-1 min-w-0'>
@@ -203,6 +204,7 @@ function OrdersIndex() {
width={64}
height={64}
alt={foodName}
unoptimized
/>
</div>
<div className='flex-1 min-w-0'>
+11 -13
View File
@@ -1,17 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { fetchWithTimeout } from "@/lib/helpers/fetchWithTimeout";
import { NextRequest, NextResponse } from "next/server";
import sharp from "sharp";
export const dynamic = "force-dynamic";
export const revalidate = 0;
const FETCH_TIMEOUT_MS = 5000;
const DEFAULT_ICON = "/icons/web-app-manifest-192x192.png";
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ name: string }> },
) {
try {
const { name } = await params;
console.log("name", name);
await params;
const searchParams = request.nextUrl.searchParams;
const logoUrl = searchParams.get("url");
@@ -21,29 +24,27 @@ export async function GET(
return new NextResponse("Logo URL is required", { status: 400 });
}
// دانلود تصویر
const imageResponse = await fetch(logoUrl);
const imageResponse = await fetchWithTimeout(logoUrl, {
timeoutMs: FETCH_TIMEOUT_MS,
});
if (!imageResponse.ok) {
return new NextResponse("Failed to fetch logo", { status: 404 });
return NextResponse.redirect(new URL(DEFAULT_ICON, request.url), 302);
}
const imageBuffer = Buffer.from(await imageResponse.arrayBuffer());
// دریافت ابعاد تصویر
const metadata = await sharp(imageBuffer).metadata();
const { width, height } = metadata;
if (!width || !height) {
return new NextResponse("Invalid image", { status: 400 });
return NextResponse.redirect(new URL(DEFAULT_ICON, request.url), 302);
}
// اگر تصویر از قبل مربع باشد، مستقیماً resize می‌کنیم
if (width === height) {
const processedImage = sharp(imageBuffer).resize(size, size, {
fit: "contain",
background: { r: 0, g: 0, b: 0, alpha: 0 },
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const outputBuffer: any = await processedImage.png().toBuffer();
return new NextResponse(outputBuffer, {
@@ -54,13 +55,11 @@ export async function GET(
});
}
// محاسبه نسبت برای resize اولیه (برای بهینه‌سازی)
const maxDimension = Math.max(width, height);
const scale = size / maxDimension;
const newWidth = Math.round(width * scale);
const newHeight = Math.round(height * scale);
// ابتدا resize می‌کنیم و سپس padding اضافه می‌کنیم
const paddingTop = Math.floor((size - newHeight) / 2);
const paddingBottom = size - newHeight - paddingTop;
const paddingLeft = Math.floor((size - newWidth) / 2);
@@ -79,7 +78,6 @@ export async function GET(
background: { r: 0, g: 0, b: 0, alpha: 0 },
});
// تبدیل به PNG و برگرداندن
const outputBuffer: any = await processedImage.png().toBuffer();
return new NextResponse(outputBuffer, {
@@ -90,6 +88,6 @@ export async function GET(
});
} catch (error) {
console.error("Error processing logo:", error);
return new NextResponse("Internal Server Error", { status: 500 });
return NextResponse.redirect(new URL(DEFAULT_ICON, request.url), 302);
}
}
+3 -1
View File
@@ -1,3 +1,4 @@
import { fetchWithTimeout } from "@/lib/helpers/fetchWithTimeout";
import { NextRequest, NextResponse } from "next/server";
export const dynamic = "force-dynamic";
@@ -15,7 +16,8 @@ export async function GET(request: NextRequest) {
if (!["http:", "https:"].includes(parsed.protocol)) {
return NextResponse.json({ error: "Invalid URL" }, { status: 400 });
}
const res = await fetch(url, {
const res = await fetchWithTimeout(url, {
timeoutMs: 5000,
headers: { Accept: "image/svg+xml, text/xml, text/plain" },
});
if (!res.ok) {
+16 -5
View File
@@ -23,6 +23,17 @@ export async function generateMetadata({
try {
const restaurant = await getRestaurant(name)
const title = restaurant.seoTitle || restaurant.name
const logo = restaurant.logo
let iconUrl = defaultIcon
let icon192Url = defaultIcon
let icon512Url = defaultIcon512
if (logo && logo.trim() !== "") {
iconUrl = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=192`
icon192Url = iconUrl
icon512Url = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=512`
}
return {
title,
@@ -30,12 +41,12 @@ export async function generateMetadata({
manifest: `/${name}/manifest.webmanifest`,
icons: {
icon: [
{ url: defaultIcon },
{ url: defaultIcon, sizes: "192x192", type: "image/png" },
{ url: defaultIcon512, sizes: "512x512", type: "image/png" },
{ url: iconUrl },
{ url: icon192Url, sizes: "192x192", type: "image/png" },
{ url: icon512Url, sizes: "512x512", type: "image/png" },
],
shortcut: defaultIcon,
apple: defaultIcon,
shortcut: iconUrl,
apple: iconUrl,
},
}
} catch {
+42 -29
View File
@@ -4,6 +4,46 @@ import { getRestaurant } from "../lib/getRestaurant";
export const dynamic = "force-dynamic";
export const revalidate = 0;
function buildManifestIcons(name: string, logo?: string | null) {
const default192 = "/icons/web-app-manifest-192x192.png";
const default512 = "/icons/web-app-manifest-512x512.png";
if (logo && logo.trim() !== "") {
const logo192 = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=192`;
const logo512 = `/${name}/api/logo?url=${encodeURIComponent(logo)}&size=512`;
return [
{
src: logo192,
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: logo512,
sizes: "512x512",
type: "image/png",
purpose: "any",
},
];
}
return [
{
src: default192,
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: default512,
sizes: "512x512",
type: "image/png",
purpose: "any",
},
];
}
export async function GET(
request: Request,
{ params }: { params: Promise<{ name: string }> }
@@ -13,21 +53,7 @@ export async function GET(
const restaurant = await getRestaurant(name);
const themeColor = restaurant.menuColor || "#F4F5F9";
const icons = [
{
src: "/icons/web-app-manifest-192x192.png",
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: "/icons/web-app-manifest-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
];
const icons = buildManifestIcons(name, restaurant.logo);
const manifest = {
name: restaurant.name,
@@ -75,20 +101,7 @@ export async function GET(
orientation: "portrait",
dir: "rtl",
lang: "fa-IR",
icons: [
{
src: "/icons/web-app-manifest-192x192.png",
sizes: "192x192",
type: "image/png",
purpose: "any",
},
{
src: "/icons/web-app-manifest-512x512.png",
sizes: "512x512",
type: "image/png",
purpose: "any",
},
],
icons: buildManifestIcons(name),
};
return NextResponse.json(manifest, {