ceo optimization
This commit is contained in:
@@ -1,14 +0,0 @@
|
|||||||
import { ContactForm } from "@/components/ContactForm";
|
|
||||||
import DetailInfo from "@/components/detailPage/detailInfo";
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
export default function Page() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DetailInfo />
|
|
||||||
<section id="contact">
|
|
||||||
<ContactForm />
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
38
app/product/[slug]/page.tsx
Normal file
38
app/product/[slug]/page.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { ContactForm } from "@/components/ContactForm";
|
||||||
|
import DetailInfo from "@/components/detailPage/detailInfo";
|
||||||
|
import { Product } from "@/lib/products";
|
||||||
|
import { generateSlug } from "@/lib/slug";
|
||||||
|
import { getAllProducts } from "@/lib/api";
|
||||||
|
import { notFound } from "next/navigation";
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
const products = await getAllProducts();
|
||||||
|
|
||||||
|
return products.map((product) => ({
|
||||||
|
slug: generateSlug(product.name_uz),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getProduct(slug: string): Promise<Product | undefined> {
|
||||||
|
const products = await getAllProducts();
|
||||||
|
|
||||||
|
return products.find((product) => generateSlug(product.name_uz) === slug);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params }: { params: any }) {
|
||||||
|
const { slug } = await params;
|
||||||
|
const product = await getProduct(slug);
|
||||||
|
|
||||||
|
if (!product) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DetailInfo product={product} />
|
||||||
|
<section id="contact">
|
||||||
|
<ContactForm />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,14 +3,16 @@
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { useLanguage } from "@/context/language-context";
|
import { useLanguage } from "@/context/language-context";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useProduct, useProductStore } from "@/lib/productZustand";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
import { Product } from "@/lib/products";
|
||||||
|
|
||||||
export default function DetailInfo() {
|
interface DetailInfoProps {
|
||||||
|
product: Product;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DetailInfo({ product }: DetailInfoProps) {
|
||||||
const { t, language } = useLanguage();
|
const { t, language } = useLanguage();
|
||||||
const languageIndex = language === "uz" ? true : false;
|
const languageIndex = language === "uz" ? true : false;
|
||||||
const product = useProduct((state) => state.product);
|
|
||||||
if (product === null) return null;
|
|
||||||
|
|
||||||
const handleScroll = (href: string) => {
|
const handleScroll = (href: string) => {
|
||||||
const element = document.querySelector(href);
|
const element = document.querySelector(href);
|
||||||
@@ -74,7 +76,7 @@ export default function DetailInfo() {
|
|||||||
{t.features}
|
{t.features}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="space-y-3 flex items-start justify-start gap-4 flex-wrap ">
|
<div className="space-y-3 flex items-start justify-start gap-4 flex-wrap ">
|
||||||
{product.features.map((spec, idx) => (
|
{product.features?.map((spec, idx) => (
|
||||||
<div
|
<div
|
||||||
key={idx}
|
key={idx}
|
||||||
className="flex flex-col justify-between py-2 border border-gray-100 rounded-lg p-2"
|
className="flex flex-col justify-between py-2 border border-gray-100 rounded-lg p-2"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
const setProducts = useProduct((state) => state.setProducts);
|
const setProducts = useProduct((state) => state.setProducts);
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={`/detail?${languageIndex ? product.name_uz : product.name_ru}`}
|
href={`/product/${product.slug}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setProductName(languageIndex ? product.name_uz : product.name_ru);
|
setProductName(languageIndex ? product.name_uz : product.name_ru);
|
||||||
setProducts(product);
|
setProducts(product);
|
||||||
@@ -63,14 +63,14 @@ export function ProductCard({ product }: ProductCardProps) {
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{/* CTA Button - Always at Bottom */}
|
{/* CTA Button - Always at Bottom */}
|
||||||
<motion.button
|
<motion.div
|
||||||
whileHover={{ scale: 1.02 }}
|
whileHover={{ scale: 1.02 }}
|
||||||
whileTap={{ scale: 0.98 }}
|
whileTap={{ scale: 0.98 }}
|
||||||
className="w-full flex items-center justify-center gap-2 px-6 py-3 bg-primary text-white rounded-xl font-semibold shadow-lg shadow-blue-500/30 hover:shadow-xl hover:shadow-primary/40 transition-all duration-300 group/button"
|
className="w-full flex items-center justify-center gap-2 px-6 py-3 bg-primary text-white rounded-xl font-semibold shadow-lg shadow-blue-500/30 hover:shadow-xl hover:shadow-primary/40 transition-all duration-300 group/button"
|
||||||
>
|
>
|
||||||
<span>{t.details}</span>
|
<span>{t.details}</span>
|
||||||
<ExternalLink className="w-4 h-4 group-hover/button:translate-x-1 transition-transform duration-300" />
|
<ExternalLink className="w-4 h-4 group-hover/button:translate-x-1 transition-transform duration-300" />
|
||||||
</motion.button>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Decorative Corner */}
|
{/* Decorative Corner */}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { ProductCard } from "./ProductCard";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import EmptyState from "../productsPage/emptyData";
|
import EmptyState from "../productsPage/emptyData";
|
||||||
import Loading from "../loading";
|
import Loading from "../loading";
|
||||||
|
import { getAllProducts } from "@/lib/api";
|
||||||
|
import { generateSlug } from "@/lib/slug";
|
||||||
|
|
||||||
// hello everyone
|
// hello everyone
|
||||||
|
|
||||||
@@ -22,14 +24,14 @@ export function ProductsGrid() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getData() {
|
async function getData() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await axios
|
const products = await getAllProducts();
|
||||||
.get("https://admin.promtechno.uz/api/products/")
|
setAllProducts(
|
||||||
.then((res) => {
|
products.map((product: any) => ({
|
||||||
console.log("all data main page: ", res?.data);
|
...product,
|
||||||
const allData = res?.data || [];
|
slug: generateSlug(product.name_uz),
|
||||||
setAllProducts(allData.slice(0, 3));
|
})).slice(0, 3)
|
||||||
setLoading(false);
|
);
|
||||||
});
|
setLoading(false);
|
||||||
}
|
}
|
||||||
getData();
|
getData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { motion } from "framer-motion";
|
|||||||
import { ProductCard } from "../productSection/ProductCard";
|
import { ProductCard } from "../productSection/ProductCard";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import Loading from "../loading";
|
import Loading from "../loading";
|
||||||
|
import { generateSlug } from "@/lib/slug";
|
||||||
|
import { getAllProducts } from "@/lib/api";
|
||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { opacity: 0, y: 20 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
@@ -18,14 +20,14 @@ export default function Products() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getData() {
|
async function getData() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await axios
|
const products = await getAllProducts();
|
||||||
.get("https://admin.promtechno.uz/api/products/")
|
setAllProducts(
|
||||||
.then((res) => {
|
products.map((product: any) => ({
|
||||||
console.log("all data: ", res?.data);
|
...product,
|
||||||
const allData = res?.data || [];
|
slug: generateSlug(product.name_uz),
|
||||||
setAllProducts(allData);
|
}))
|
||||||
setLoading(false);
|
);
|
||||||
});
|
setLoading(false);
|
||||||
}
|
}
|
||||||
getData();
|
getData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
14
lib/api.ts
Normal file
14
lib/api.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Product } from "@/lib/products";
|
||||||
|
|
||||||
|
export async function getAllProducts(): Promise<Product[]> {
|
||||||
|
const res = await fetch("https://admin.promtechno.uz/api/products/", {
|
||||||
|
cache: "force-cache", // build time uchun
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.log("Failed to fetch products");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.json();
|
||||||
|
}
|
||||||
17
lib/generateStaticParam.ts
Normal file
17
lib/generateStaticParam.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { generateSlug } from "./slug";
|
||||||
|
|
||||||
|
export async function generateStaticParams() {
|
||||||
|
try {
|
||||||
|
await axios.get("https://admin.promtechno.uz/api/products/").then((res) => {
|
||||||
|
console.log("all data: ", res?.data);
|
||||||
|
const allData = res?.data || [];
|
||||||
|
return allData.map((product: any) => ({
|
||||||
|
slug: generateSlug(product.name_uz),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in generateStaticParams:", error);
|
||||||
|
return []; // Xato bo'lsa bo'sh array qaytarish
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,18 +9,11 @@ interface ProductStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const useProductStore = create<ProductStore>()(
|
export const useProductStore = create<ProductStore>()(
|
||||||
devtools(
|
devtools((set) => ({
|
||||||
persist(
|
productName: "",
|
||||||
(set) => ({
|
setProductName: (name: string) => set({ productName: name }),
|
||||||
productName: "",
|
resetProductName: () => set({ productName: "" }),
|
||||||
setProductName: (name: string) => set({ productName: name }),
|
}))
|
||||||
resetProductName: () => set({ productName: "" }),
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
name: "product-storage",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
interface ProductDetail {
|
interface ProductDetail {
|
||||||
|
|||||||
@@ -13,4 +13,5 @@ export interface Product {
|
|||||||
description_ru: string;
|
description_ru: string;
|
||||||
features:features[];
|
features:features[];
|
||||||
image:string;
|
image:string;
|
||||||
|
slug?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
12
lib/slug.ts
Normal file
12
lib/slug.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export function generateSlug(productName: string): string {
|
||||||
|
return productName
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\s+/g, "-") // Bo'shliqlarni tire bilan almashtirish
|
||||||
|
.replace(/[()]/g, "") // Qavslarni olib tashlash
|
||||||
|
.replace(/[–—]/g, "-") // Maxsus tire'larni oddiy tire bilan
|
||||||
|
.replace(/%/g, "foiz") // % ni foiz deb yozish
|
||||||
|
.replace(/,/g, "-") // Vergullarni tire bilan
|
||||||
|
.replace(/\.+/g, "-") // Nuqtalarni tire bilan
|
||||||
|
.replace(/-+/g, "-") // Bir nechta tire'ni bitta tire bilan
|
||||||
|
.replace(/^-|-$/g, ""); // Boshi va oxiridagi tire'larni olib tashlash
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user