feat: Add Docker support and improve API structure

- Introduced Dockerfile and .dockerignore for containerization.
- Removed unused API route `src/app/api/hello/route.ts`.
- Added new API route for fetching products by category.
- Enhanced existing product category API to include descriptions.
- Updated product categories data structure to include descriptions.
- Improved product fetching logic in the product categories page.
- Refactored breadcrumb component for better navigation.
- Added new AchievementIconWhite component for UI consistency.
- Updated Navbar to use routeFactory for better maintainability.
- Enhanced styling in various components for improved responsiveness.
- Removed obsolete language switcher component.
This commit is contained in:
2025-06-05 16:03:59 +03:30
parent d44aa203e0
commit 0bd5060580
28 changed files with 403 additions and 111 deletions
+4
View File
@@ -0,0 +1,4 @@
node_modules
npm-debug.log
.next/cache
+25
View File
@@ -0,0 +1,25 @@
# Use official Node.js image as the base
FROM node:20-alpine AS builder
# Set working directory
WORKDIR /app
# Install dependencies
COPY package.json package-lock.json ./
RUN npm ci
# Copy the rest of the app
COPY . .
# Build the Next.js app
RUN npm run build
# Set environment variables (optional)
ENV NODE_ENV=production
# Expose port
EXPOSE 5000
# Start the Next.js app
CMD ["npm", "start"]
Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

-10
View File
@@ -1,10 +0,0 @@
import { NextResponse } from 'next/server';
export async function GET(request: Request) {
return NextResponse.json({ message: 'Hello from Next.js API Route!' });
}
export async function POST(request: Request) {
const data = await request.json();
return NextResponse.json({ message: 'Received POST request', data });
}
@@ -0,0 +1,29 @@
import { NextResponse, NextRequest } from 'next/server';
// import type { NextApiRequestContext } from 'next';
import { cookies } from 'next/headers';
import { productCategories } from '../../data';
import images from '@/assets/images/images';
export async function GET(request: NextRequest, context: any) {
const cookieStore = await cookies();
const lang = cookieStore.get('lang')?.value || 'en';
const category = productCategories.find(
(cat) => cat.id === context.params.id,
);
if (!category) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const product = {
title: 'Product title',
size: 'W 18 * L10',
imageSrc: '/product1.png',
};
const products = Array.from({ length: 10 }, () => product);
return NextResponse.json({
data: products,
});
}
+12 -6
View File
@@ -1,15 +1,16 @@
import { NextResponse } from 'next/server';
import { NextResponse, NextRequest } from 'next/server';
// import type { NextApiRequestContext } from 'next';
import { cookies } from 'next/headers';
import { productCategories } from '../data';
export async function GET(
request: Request,
{ params }: { params: { id: string } },
) {
export async function GET(request: NextRequest, context: any) {
const cookieStore = await cookies();
const lang = cookieStore.get('lang')?.value || 'en';
const category = productCategories.find((cat) => cat.id === params.id);
const category = productCategories.find(
(cat) => cat.id === context.params.id,
);
if (!category) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
@@ -25,6 +26,10 @@ export async function GET(
lang === 'fa' && category.fa_bestForTitle
? category.fa_bestForTitle
: category.bestForTitle;
const descriptionData =
lang === 'fa' && category.fa_description
? category.fa_description
: category.description;
return NextResponse.json({
data: {
@@ -34,6 +39,7 @@ export async function GET(
title: titleData,
priceTypeTitle: priceTypeTitleData,
bestForTitle: bestForTitleData,
description: descriptionData,
},
});
}
+60
View File
@@ -9,6 +9,18 @@ export const productCategories = [
fa_priceTypeTitle: 'اقتصادی',
bestForTitle: 'Best For Interior Part',
fa_bestForTitle: 'Best For Interior Part',
fa_description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
},
{
id: '2',
@@ -20,6 +32,18 @@ export const productCategories = [
fa_priceTypeTitle: 'لاکچری',
bestForTitle: 'Best For Exterior Part',
fa_bestForTitle: 'Best For Exterior Part',
fa_description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
},
{
id: '3',
@@ -31,6 +55,18 @@ export const productCategories = [
fa_priceTypeTitle: 'اقتصادی',
bestForTitle: 'Best For Facing',
fa_bestForTitle: 'Best For Facing',
fa_description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
},
{
id: '40',
@@ -42,6 +78,18 @@ export const productCategories = [
fa_priceTypeTitle: 'اقتصادی',
bestForTitle: 'Best For Building',
fa_bestForTitle: 'Best For Building',
fa_description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
description: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`,
},
] as APIProductCategoryEntity[];
@@ -55,4 +103,16 @@ interface APIProductCategoryEntity {
fa_priceTypeTitle?: string;
bestForTitle: string;
fa_bestForTitle?: string;
fa_description?: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`;
description?: `Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features
Facing bricks, also known as face bricks or veneer bricks, are specially designed for visible areas of a building, primarily the exterior facades, but also for interior walls where aesthetics are important. Unlike common bricks which are often covered by plaster or other materials, facing bricks are chosen for their visual appeal and ability to withstand environmental conditions.
Facing Bricks: Characteristics and Features`;
}
+6 -1
View File
@@ -5,7 +5,7 @@ import { productCategories } from './data';
export async function GET(request: Request) {
let data = productCategories;
const cookieStore = cookies();
const cookieStore = await cookies();
const lang = (await cookieStore).get('lang')?.value || 'fa';
if (lang === 'fa') {
@@ -20,6 +20,8 @@ export async function GET(request: Request) {
fa_priceTypeTitle,
bestForTitle,
fa_bestForTitle,
description,
fa_description,
}) => {
const titleData = lang === 'fa' && fa_title ? fa_title : title;
const priceTypeTitleData =
@@ -28,6 +30,8 @@ export async function GET(request: Request) {
: priceTypeTitle;
const bestForTitleData =
lang === 'fa' && fa_bestForTitle ? fa_bestForTitle : bestForTitle;
const descriptionData =
lang === 'fa' && fa_description ? fa_description : description;
return {
id,
icon,
@@ -35,6 +39,7 @@ export async function GET(request: Request) {
title: titleData,
priceTypeTitle: priceTypeTitleData,
bestForTitle: bestForTitleData,
description: descriptionData,
};
},
);
+40 -1
View File
@@ -106,10 +106,19 @@
}
* {
font-family: 'Manrope';
}
html{
font-size: 16px;
}
@media (width <=48rem) {
html {
font-size: 14px;
}
}
:root {
--primary: #FA003F;
@@ -185,3 +194,33 @@
padding-inline: 8rem;
}
}
.container-fluid {
width: 100%;
padding-inline: 1rem;
@media (width >=40rem) {
max-width: 100%;
padding-inline: 1rem;
}
@media (width >=48rem) {
max-width: 100%;
padding-inline: 1.5rem;
}
@media (width >=64rem) {
max-width: 100%;
padding-inline: 2rem;
}
@media (width >=80rem) {
max-width: 100%;
padding-inline: 3rem;
}
@media (width >=96rem) {
max-width: 100%;
padding-inline: 5rem;
}
}
@@ -1,36 +1,57 @@
import routeFactory from '@/assets/constants/routeFactory';
import images from '@/assets/images/images';
import Breadcrumb from '@/components/layout/breadcrumb';
import { IBreadcrumbItem } from '@/components/layout/breadcrumb/breadcrumb';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import ProductCategoryCardInProducts from '@/components/pages/productCategories/ProductCategoryCardInProducts';
import ProductCard from '@/components/pages/products/ProductCard';
import { getProductCategoryById } from '@/services/products';
import { getProductCategoryById, getProducts } from '@/services/products';
interface IParams {
product_category_id: string;
}
export default async function ProductPage({
params,
}: {
params: { product_category_id: string };
params: Promise<IParams>;
}) {
const { product_category_id: productCategoryId } = params;
const { product_category_id: productCategoryId } = await params;
const productCategory = await getProductCategoryById(productCategoryId);
const products = await getProducts(productCategoryId);
const breadcrumbItems = [
{
...routeFactory.productCategories,
href: routeFactory.productCategories.route(),
},
{ title: productCategory.title },
] as IBreadcrumbItem[];
const product = {
title: 'Product title',
size: 'W 18 * L10',
imageSrc: images.product1.src,
};
const products = Array.from({ length: 10 }, () => product);
return (
<main className="">
<InnerPageBanner
title={productCategory.title}
imageSrc={images.homeProductBack.src}
/>
<div className="container mx-auto my-24">
<div className="grid grid-cols-4 gap-3">
<div className="container-fluid mx-auto my-24">
<Breadcrumb items={breadcrumbItems} />
<ProductCategoryCardInProducts
productCategory={productCategory}
className="mt-6"
/>
<div className="mt-20">
<h2 className="mb-6 text-3xl font-semibold text-gray-500">
{productCategory.title} Products
</h2>
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
{products.map((product, index) => (
<ProductCard {...product} key={index} />
))}
</div>
</div>
</div>
</main>
);
}
+3 -3
View File
@@ -12,11 +12,11 @@ export default async function ProductsPage() {
return (
<main>
<InnerPageBanner
title={t('pages_projects_title') as string}
title={t('pages_product_categories_title') as string}
imageSrc={images.aboutBanner.src}
/>
<div className="container mx-auto my-24">
<div className="grid w-full grid-cols-4 gap-4">
<div className="container-fluid mx-auto my-24">
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
{productCategories.map((category) => (
<ProductsCategoryCard key={category.id} {...category} />
))}
+7 -7
View File
@@ -2,31 +2,31 @@ import { t, translates } from '@/locales/translates';
export default {
home: {
title: t('pages_home_title'),
title: t('pages_home_title') as string,
route: () => '/',
},
productCategories: {
title: t('pages_product_categories_title'),
title: t('pages_product_categories_title') as string,
route: () => '/product-categories',
},
products: {
title: t('pages_products_title'),
title: t('pages_products_title') as string,
route: (productId: string) => `/product-categories/${productId}`,
},
projects: {
title: t('pages_projects_title'),
title: t('pages_projects_title') as string,
route: () => '/projects',
},
about: {
title: t('pages_about_title'),
title: t('pages_about_title') as string,
route: () => '/about-us',
},
blog: {
title: t('pages_blog_title'),
title: t('pages_blog_title') as string,
route: () => '/blog-us',
},
contact: {
title: t('pages_contact_title'),
title: t('pages_contact_title') as string,
route: () => '/contact',
},
};
@@ -1,3 +1,4 @@
export interface IAachievementTextBoxProps {
title: string;
reverseColor?: boolean;
}
@@ -3,11 +3,19 @@ import { IAachievementTextBoxProps } from './achievementTextBox';
export default function AchievementTextBox({
title,
reverseColor,
}: IAachievementTextBoxProps) {
return (
<div className="flex items-center gap-3">
<Icon name="achievement" className="text-primary" />
<span className="text-base text-gray-500">{title}</span>
<Icon
name={reverseColor ? 'achievementWhite' : 'achievement'}
className={`${reverseColor ? 'text-white' : 'text-primary'}`}
/>
<span
className={`text-base ${reverseColor ? 'text-white' : 'text-gray-500'}`}
>
{title}
</span>
</div>
);
}
+8
View File
@@ -0,0 +1,8 @@
export interface IBreadcrumbProps {
items: IBreadcrumbItem[];
}
export interface IBreadcrumbItem {
title: string;
href?: string;
}
@@ -0,0 +1,30 @@
import Link from 'next/link';
import { IBreadcrumbProps } from './breadcrumb';
export default function Breadcrumb({ items }: IBreadcrumbProps) {
return (
<nav aria-label="breadcrumb">
<div className="flex items-center gap-2">
{items.map((item, index) => (
<div key={index} className="flex items-center gap-2">
{index > 0 && (
<span className="text-sm font-normal text-gray-300"></span>
)}
{item.href ? (
<Link
href={item.href}
className="text-base font-normal text-gray-300"
>
{item.title}
</Link>
) : (
<span className="text-primary text-base font-bold">
{item.title}
</span>
)}
</div>
))}
</div>
</nav>
);
}
@@ -1,17 +0,0 @@
'use client';
import { changeLanguage, TLocales } from '@/locales/translates';
import Button from '../Button';
export default function ChangeLangButton() {
const changeLang = (lang: TLocales) => {
changeLanguage(lang);
window.location.reload(); // Reload to get new SSR content
};
return (
<>
<Button onClick={() => changeLang('en')}>English</Button>
<Button onClick={() => changeLang('fa')}>فارسی</Button>
</>
);
}
+12 -11
View File
@@ -94,18 +94,19 @@ import { usePathname } from 'next/navigation';
import { useState } from 'react';
import HamburgerMenu from '../hamburgerMenu';
import { t } from '@/locales/translates';
import routeFactory from '@/assets/constants/routeFactory';
export default function Navbar() {
const [open, setOpen] = useState(false);
const pathname = usePathname();
const links = [
{ href: '/', label: t('pages_home_title') as string },
{ href: '/about', label: t('pages_about_title') as string },
{ href: '/blog', label: t('pages_blog_title') as string },
{ href: '/contact', label: t('pages_contact_title') as string },
{ href: '/product', label: t('pages_product_categories_title') as string },
{ href: '/project', label: t('pages_projects_title') as string },
routeFactory.home,
routeFactory.productCategories,
routeFactory.projects,
routeFactory.blog,
routeFactory.about,
routeFactory.contact,
];
const linkClass = (href: string) =>
pathname === href ? 'text-primary font-semibold' : 'text-white';
@@ -123,13 +124,13 @@ export default function Navbar() {
</Link>
<ul className="hidden space-x-6 text-sm md:flex lg:text-base">
{links.map(({ href, label }) => (
<li key={href}>
{links.map(({ route, title }) => (
<li key={route()}>
<Link
href={href}
className={`${linkClass(href)} hover:text-primary-light`}
href={route()}
className={`${linkClass(route())} hover:text-primary-light`}
>
{label}
{title}
</Link>
</li>
))}
+2 -1
View File
@@ -1,3 +1,4 @@
import routeFactory from '@/assets/constants/routeFactory';
import images from '@/assets/images/images';
import AchievementTextBox from '@/components/layout/achievementTextBox';
import SectionTitle from '@/components/layout/sectionTitle';
@@ -60,7 +61,7 @@ export default function AboutTopInfo() {
))}
</div>
<div className="mt-10">
<Button endIcon="showMore">
<Button endIcon="showMore" link={routeFactory.contact.route()}>
{t('pages_contact_title') as string}
</Button>
</div>
+10 -20
View File
@@ -1,26 +1,14 @@
import images from '@/assets/images/images';
import SectionTitle from '@/components/layout/sectionTitle';
import { t } from '@/locales/translates';
import type { IProductCategoryCardProps } from '../productCategories/productCategoryCard.d';
import { getProductCategories } from '@/services/products';
import Link from 'next/link';
import routeFactory from '@/assets/constants/routeFactory';
export default function HomePageProducts() {
const productCategories = [
{
title: 'Brick Categories',
count: 30,
},
{
title: 'Brick Categories',
count: 30,
},
{
title: 'Brick Categories',
count: 30,
},
{
title: 'Brick Categories',
count: 30,
},
];
export default async function HomePageProducts() {
const productCategories: IProductCategoryCardProps[] =
await getProductCategories();
return (
<div className="w-full">
@@ -44,6 +32,7 @@ export default function HomePageProducts() {
<div className="relative top-[-100px] z-20 container mx-auto w-full">
<div className="grid grid-cols-1 gap-8 rounded-4xl bg-gray-50 py-10 shadow-2xl md:grid-cols-2 lg:grid-cols-4">
{productCategories.map((category, index) => (
<Link href={routeFactory.products.route(category.id)}>
<div
key={index}
className={`flex flex-col items-center justify-center gap-4 p-6 text-center ${index ? 'border-s-[1px] border-gray-100' : ''}`}
@@ -52,9 +41,10 @@ export default function HomePageProducts() {
{category.title}
</h3>
<p className="text-lg font-light text-gray-400">
{`Includes ${category.count} Products`}
{`Includes ${category.typesCount} Products`}
</p>
</div>
</Link>
))}
</div>
</div>
@@ -14,7 +14,7 @@ export default function ProductsCategoryCard({
id,
}: IProductCategoryCardProps) {
return (
<div className="relative overflow-hidden rounded-4xl bg-gray-100 p-10">
<div className="relative overflow-hidden rounded-4xl bg-gray-100 p-3 sm:p-6 md:p-10">
<div className="absolute -end-2 -top-2">
<Icon name={icon} className="text-primary h-36 w-36" />
</div>
@@ -1,9 +1,58 @@
import AchievementTextBox from '@/components/layout/achievementTextBox';
import type { IProductCategoryCardProps } from './productCategoryCard.d';
import { t } from '@/locales/translates';
import { Icon } from '@/components/uikit/icons';
export default function ProductCategoryCardInProducts(
productCategory: IProductCategoryCardProps,
) {
export default function ProductCategoryCardInProducts({
productCategory,
className,
}: {
productCategory: IProductCategoryCardProps;
className?: string;
}) {
return (
<div className="bg-primary relative overflow-hidden rounded-4xl p-20 pe-40"></div>
<div className="">
<div
className={`bg-primary relative grid overflow-hidden rounded-4xl p-6 max-lg:pt-24 max-lg:pb-12 lg:grid-cols-2 lg:p-20 lg:pe-40 ${className}`}
>
<div className="absolute -end-2 -top-2">
<Icon
name={productCategory.icon}
className="h-24 w-24 text-white md:h-36 md:w-36"
/>
</div>
<div className="flex flex-col gap-5">
<h1 className="text-2xl font-semibold text-white">
{
t('com_products_products_category_title', {
title: productCategory.title,
}) as string
}
</h1>
<AchievementTextBox
reverseColor
title={
t('com_products_category_types_count', {
count: productCategory.typesCount,
}) as string
}
/>
<AchievementTextBox
reverseColor
title={productCategory.priceTypeTitle}
/>
<AchievementTextBox
reverseColor
title={productCategory.bestForTitle}
/>
</div>
<p className="text-xl text-white max-lg:hidden">
{productCategory.description}
</p>
</div>
<p className="mt-5 text-xl text-gray-500 lg:hidden">
{productCategory.description}
</p>
</div>
);
}
@@ -7,4 +7,5 @@ export interface IProductCategoryCardProps {
typesCount: number;
priceTypeTitle: string;
bestForTitle: string;
description: string;
}
+1 -1
View File
@@ -13,7 +13,7 @@ const Button: React.FC<ITextButtonProps> = ({
link,
...props
}) => {
const wrapperClass = `group bg-primary relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex ${className || ''}`;
const wrapperClass = `group bg-primary relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''}`;
return link ? (
<Link href={link} className={wrapperClass} {...props}>
@@ -0,0 +1,23 @@
export default function AchievementIconWhite(
props: React.SVGProps<SVGSVGElement>,
) {
return (
<svg
{...props}
width="24"
height="25"
viewBox="0 0 24 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M23.878 10.9739C23.8379 10.6942 23.6342 10.4657 23.361 10.3939L22.1659 10.0794C21.92 9.11899 21.5402 8.20366 21.033 7.34871L21.6566 6.27949C21.7988 6.03546 21.7813 5.73002 21.612 5.5039C21.2711 5.04837 20.892 4.61116 20.4853 4.20429C20.0795 3.79835 19.6422 3.41923 19.1856 3.07732C18.9595 2.90801 18.654 2.89043 18.4099 3.03288L17.3408 3.65651C16.4859 3.14923 15.5706 2.76949 14.6102 2.52354L14.2958 1.32855C14.2238 1.05532 13.9954 0.851648 13.7157 0.811476C12.5876 0.64943 11.4123 0.64943 10.2842 0.811476C10.0045 0.851601 9.77611 1.05527 9.7042 1.32855L9.38972 2.52354C8.42939 2.76944 7.51416 3.14918 6.65911 3.65651L5.59008 3.03288C5.34605 2.89052 5.04047 2.9081 4.81434 3.07732C4.35783 3.41918 3.92053 3.7983 3.51464 4.20429C3.10795 4.61112 2.72888 5.04832 2.38791 5.50385C2.21864 5.72998 2.20111 6.03541 2.34338 6.27944L2.96691 7.34866C2.45967 8.20371 2.07994 9.11899 1.83403 10.0793L0.639 10.3938C0.365719 10.4657 0.162047 10.6942 0.121969 10.9739C0.0410625 11.5382 0 12.1154 0 12.6896C0 13.2638 0.0410625 13.841 0.122016 14.4053C0.162094 14.685 0.365766 14.9135 0.639047 14.9854L1.83408 15.2999C2.08003 16.2603 2.45977 17.1756 2.96695 18.0305L2.34342 19.0998C2.20116 19.3438 2.21869 19.6492 2.38795 19.8754C2.72892 20.3309 3.108 20.7681 3.51473 21.175C3.92053 21.5809 4.35783 21.96 4.81439 22.3019C5.04052 22.4712 5.346 22.4888 5.59012 22.3464L6.65916 21.7227C7.51411 22.23 8.42944 22.6098 9.38977 22.8557L9.70425 24.0507C9.77616 24.3239 10.0046 24.5276 10.2843 24.5677C10.8483 24.6488 11.4256 24.6899 12 24.6899C12.5744 24.6899 13.1517 24.6488 13.7158 24.5678C13.9955 24.5277 14.2239 24.324 14.2958 24.0507L14.6103 22.8558C15.5706 22.6099 16.4858 22.2301 17.3409 21.7228L18.4099 22.3465C18.654 22.4889 18.9596 22.4713 19.1857 22.302C19.6422 21.9602 20.0795 21.581 20.4854 21.1751C20.892 20.7682 21.2711 20.331 21.6121 19.8755C21.7814 19.6494 21.7989 19.3439 21.6566 19.0999L21.0331 18.0307C21.5403 17.1756 21.9201 16.2604 22.166 15.3L23.361 14.9855C23.6343 14.9137 23.838 14.6851 23.878 14.4054C23.959 13.8411 24 13.2639 24 12.6897C24 12.1156 23.9589 11.5382 23.878 10.9739ZM12 20.1711C7.87481 20.1711 4.51875 16.8151 4.51875 12.6899C4.51875 8.56474 7.87481 5.20868 12 5.20868C16.1252 5.20868 19.4813 8.56474 19.4813 12.6899C19.4813 16.8151 16.1252 20.1711 12 20.1711Z"
fill="#F5F5F5"
/>
<path
d="M12.0008 6.61475C8.651 6.61475 5.92578 9.33997 5.92578 12.6898C5.92578 16.0395 8.651 18.7648 12.0008 18.7648C15.3506 18.7648 18.0758 16.0395 18.0758 12.6898C18.0758 9.33997 15.3506 6.61475 12.0008 6.61475ZM15.5105 11.6807L11.7448 15.4463C11.613 15.5782 11.4342 15.6523 11.2477 15.6523C11.0612 15.6523 10.8823 15.5782 10.7505 15.4463L8.86766 13.5635C8.59306 13.289 8.59306 12.8437 8.86766 12.5691C9.1422 12.2946 9.58742 12.2946 9.86202 12.5691L11.2477 13.9548L14.5161 10.6863C14.7907 10.4118 15.2359 10.4118 15.5105 10.6863C15.785 10.9609 15.785 11.4061 15.5105 11.6807Z"
fill="#F5F5F5"
/>
</svg>
);
}
+2
View File
@@ -10,12 +10,14 @@ import CategoryGhazaghiIcon from './categoryGhazaghiIcon';
import CategoryGhermezIcon from './categoryGhermezIcon';
import categorySemiromIcon from './categorySemiromIcon';
import CategoryZardIcon from './categoryZardIcon';
import AchievementIconWhite from './achievementIconWhite';
export const icons = {
close: CloseIcon,
menu: MenuIcon,
setting: SettingIcon,
achievement: AchievementIcon,
achievementWhite: AchievementIconWhite,
phone: PhoneIcon,
instagram: InstagramIcon,
showMore: ShowMoreIcon,
+3
View File
@@ -72,6 +72,9 @@ Perforated/Hollow Bricks: These bricks have holes, offering several advantages:
Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength.
2. Classification by Raw Material Type and Composition: From Clay to Concrete
The material from which a brick is made determines its physical and chemical properties.`,
com_products_products_category_title: ({ title }: { title: string }) =>
`Products of ${title} Category`,
};
export default en;
+14 -1
View File
@@ -1,3 +1,5 @@
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
import type { IProductCardProps } from '@/components/pages/products/productCard.d';
import api from '@/lib/axios';
export function getProductCategories() {
@@ -9,7 +11,9 @@ export function getProductCategories() {
.catch((error) => Promise.reject(error));
}
export function getProductCategoryById(categoryId: string) {
export function getProductCategoryById(
categoryId: string,
): Promise<IProductCategoryCardProps> {
return api
.get(`/product_categories/${categoryId}`)
.then(({ data }) => {
@@ -17,3 +21,12 @@ export function getProductCategoryById(categoryId: string) {
})
.catch((error) => Promise.reject(error));
}
export function getProducts(categoryId: string): Promise<IProductCardProps[]> {
return api
.get(`/product_categories/${categoryId}/products`)
.then(({ data }) => {
return Promise.resolve(data.data);
})
.catch((error) => Promise.reject(error));
}