Notes
Notes - notes.io |
import React, { useEffect, useState, useMemo } from "react";
import { useRouter } from "next/navigation";
import Image from "next/image";
import { Card } from "@/components/ui/card";
import { AspectRatio } from "@/components/ui/aspect-ratio";
import AddToCasketButton from "@/components/add-to-casket";
// Badge generator based on product ID
function getProductBadge(id: number) {
if (!id) return null;
const r = id % 6;
switch (r) {
case 0:
return { label: "Нас 3+", color: "bg-red-500" };
case 1:
return { label: "Нас 6+", color: "bg-blue-600" };
case 2:
return { label: "Нас 12+", color: "bg-purple-600" };
case 3:
return { label: "Ээжүүдэд", color: "bg-pink-600" };
case 4:
return { label: "Нярайд", color: "bg-yellow-500 text-black" };
default:
return null;
}
}
interface HomeProductsGridProps {
limit?: number;
title?: string;
showSeeAll?: boolean;
}
export default function HomeProductsGrid({
limit = 12,
title = "Бүх бараа",
showSeeAll = true,
}: HomeProductsGridProps) {
const router = useRouter();
const [products, setProducts] = useState<any[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(`/api/products?page=1&limit=${limit}`)
.then((res) => {
if (!res.ok) throw new Error(`status ${res.status}`);
return res.json();
})
.then((json) => {
const list: any[] = (json.results || []).slice(0, limit);
setProducts(list);
})
.catch((e) => {
console.error("HomeProductsGrid fetch error", e);
setError("Failed to load products");
})
.finally(() => setLoading(false));
}, [limit]);
const productItems = useMemo(() => {
return products.map((item) => {
const imageSrc = item.placeholder_image
? `${process.env.NEXT_PUBLIC_BACKEND_FILE}${item.placeholder_image}`
: item.files?.[0]?.image
? `${process.env.NEXT_PUBLIC_BACKEND_FILE}${item.files[0].image}`
: "/globe.png";
return (
<Card
key={item.id}
className="p-0 cursor-pointer transition-all duration-300 ease-in-out transform hover:-translate-y-1 hover:shadow-xl flex flex-col h-full rounded-xl overflow-hidden border-0 shadow-md group-hover:shadow-lg bg-white"
onClick={() => router.push(`/products/${item.id}`)}
>
<div className="flex-grow relative">
<AspectRatio ratio={4 / 5} className="relative bg-gradient-to-br from-gray-50 to-gray-100">
<Image
src={imageSrc}
alt={item.title}
fill
className="object-cover transition-transform duration-300 group-hover:scale-105"
onError={(e) => {
(e.target as HTMLImageElement).src = "/globe.png";
}}
/>
{item?.sale_percent && (
<div className="absolute top-2 left-2 bg-red-500 text-white text-xs font-bold px-2 py-1 rounded-full shadow-lg">
-{item.sale_percent}%
</div>
)}
{(() => {
const badge = getProductBadge(item.id);
if (!badge) return null;
return (
<div
className={`absolute bottom-3 right-3 z-10 ${badge.color} font-bold px-3 py-1 shadow-lg text-xs`}
style={{
clipPath: "polygon(12px 0, 100% 0, 100% 100%, 0 100%)",
}}
>
{badge.label}
</div>
);
})()}
</AspectRatio>
</div>
<div className="p-3 bg-white">
<h4 className="text-sm font-semibold truncate mb-2 text-gray-900 leading-tight line-clamp-2 min-h-[2.5rem]">
{item.title}
</h4>
<div className="space-y-1">
{item?.sale_percent ? (
<div className="flex items-center justify-between">
<div className="flex flex-col">
<span className="font-bold text-base text-[#064690]">
{(item?.price * (1 - item?.sale_percent / 100)).toLocaleString()}
₮
</span>
<span className="line-through text-gray-400 text-xs">
{item?.price?.toLocaleString()}₮
</span>
</div>
<div className="transition-transform duration-200 hover:scale-110">
<AddToCasketButton btnType="icon" productId={item?.id} />
</div>
</div>
) : (
<div className="flex items-center justify-between">
<span className="font-bold text-base text-[#064690]">
{item?.price?.toLocaleString()}₮
</span>
<div className="transition-transform duration-200 hover:scale-110">
<AddToCasketButton btnType="icon" productId={item?.id} />
</div>
</div>
)}
</div>
</div>
</Card>
);
});
}, [products, router]);
const skeletons = (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
{Array.from({ length: limit }).map((_, i) => (
<div key={i} className="animate-pulse">
<div className="bg-gray-200 rounded-xl h-64 mb-3"></div>
<div className="space-y-2">
<div className="bg-gray-200 h-4 rounded w-3/4"></div>
<div className="bg-gray-200 h-4 rounded w-1/2"></div>
<div className="flex justify-between items-center">
<div className="bg-gray-200 h-5 rounded w-16"></div>
<div className="bg-gray-200 h-8 w-8 rounded-full"></div>
</div>
</div>
</div>
))}
</div>
);
if (products.length === 0 && !loading) return null;
return (
<div className="py-8 bg-gray-50/30">
<div className="bg-white rounded-2xl shadow-lg max-w-screen-xl mx-auto px-6 py-6">
<div className="flex justify-between items-center mb-6">
<div className="flex items-center space-x-3">
<div className="w-1 h-8 bg-gradient-to-b from-[#064690] to-blue-600 rounded-full"></div>
<h2 className="text-2xl font-bold text-gray-900 tracking-tight">{title}</h2>
</div>
{showSeeAll && (
<button
onClick={() => router.push(`/products`)}
className="text-sm font-medium text-[#064690] hover:text-blue-700 transition-colors duration-200 flex items-center space-x-1 group"
>
<span>Бүгд үзэх</span>
<i className="fa-solid fa-arrow-right text-xs group-hover:translate-x-1 transition-transform duration-200"></i>
</button>
)}
</div>
{loading ? (
skeletons
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4">
{productItems}
</div>
)}
{error && (
<div className="text-center py-8 text-red-500">{error}</div>
)}
</div>
<style jsx>{`
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
`}</style>
</div>
);
}
![]() |
Notes is a web-based application for online taking notes. You can take your notes and share with others people. If you like taking long notes, notes.io is designed for you. To date, over 8,000,000,000+ notes created and continuing...
With notes.io;
- * You can take a note from anywhere and any device with internet connection.
- * You can share the notes in social platforms (YouTube, Facebook, Twitter, instagram etc.).
- * You can quickly share your contents without website, blog and e-mail.
- * You don't need to create any Account to share a note. As you wish you can use quick, easy and best shortened notes with sms, websites, e-mail, or messaging services (WhatsApp, iMessage, Telegram, Signal).
- * Notes.io has fabulous infrastructure design for a short link and allows you to share the note as an easy and understandable link.
Fast: Notes.io is built for speed and performance. You can take a notes quickly and browse your archive.
Easy: Notes.io doesn’t require installation. Just write and share note!
Short: Notes.io’s url just 8 character. You’ll get shorten link of your note when you want to share. (Ex: notes.io/q )
Free: Notes.io works for 14 years and has been free since the day it was started.
You immediately create your first note and start sharing with the ones you wish. If you want to contact us, you can use the following communication channels;
Email: [email protected]
Twitter: http://twitter.com/notesio
Instagram: http://instagram.com/notes.io
Facebook: http://facebook.com/notesio
Regards;
Notes.io Team
