update project api and page, set pagination in blog and create content generator component, create blog single page, ui fix

This commit is contained in:
2025-06-19 15:48:47 +03:30
parent 38df8ec335
commit e69e2bf7e1
34 changed files with 984 additions and 137 deletions
+2
View File
@@ -104,6 +104,7 @@ declare const messages: {
"page_about_metadata_title": "About Us", "page_about_metadata_title": "About Us",
"page_about_metadata_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.", "page_about_metadata_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.",
"page_about_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.",
"page_home_metadata_title": "Home", "page_home_metadata_title": "Home",
"page_home_metadata_description": "Welcome to our official website, where we showcase our expertise, quality, and innovation in brick manufacturing.", "page_home_metadata_description": "Welcome to our official website, where we showcase our expertise, quality, and innovation in brick manufacturing.",
@@ -116,6 +117,7 @@ declare const messages: {
"page_blog_metadata_title": "Blog", "page_blog_metadata_title": "Blog",
"page_blog_metadata_description": "Stay informed with our latest articles, news, and tips related to bricks and building materials.", "page_blog_metadata_description": "Stay informed with our latest articles, news, and tips related to bricks and building materials.",
"page_blog_details_CTA": "Show Details",
"page_projects_metadata_title": "Projects", "page_projects_metadata_title": "Projects",
"page_projects_metadata_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.", "page_projects_metadata_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.",
+2
View File
@@ -101,6 +101,7 @@
"page_about_metadata_title": "About Us", "page_about_metadata_title": "About Us",
"page_about_metadata_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.", "page_about_metadata_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.",
"page_about_description": "Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques. Learn more about our commitment to producing high-quality bricks using both traditional and modern manufacturing techniques.",
"page_home_metadata_title": "Home", "page_home_metadata_title": "Home",
"page_home_metadata_description": "Welcome to our official website, where we showcase our expertise, quality, and innovation in brick manufacturing.", "page_home_metadata_description": "Welcome to our official website, where we showcase our expertise, quality, and innovation in brick manufacturing.",
@@ -113,6 +114,7 @@
"page_blog_metadata_title": "Blog", "page_blog_metadata_title": "Blog",
"page_blog_metadata_description": "Stay informed with our latest articles, news, and tips related to bricks and building materials.", "page_blog_metadata_description": "Stay informed with our latest articles, news, and tips related to bricks and building materials.",
"page_blog_details_CTA": "Show Details",
"page_projects_metadata_title": "Projects", "page_projects_metadata_title": "Projects",
"page_projects_metadata_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.", "page_projects_metadata_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.",
+2
View File
@@ -98,6 +98,7 @@
"page_about_metadata_title": "درباره ما", "page_about_metadata_title": "درباره ما",
"page_about_metadata_description": "با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته.", "page_about_metadata_description": "با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته.",
"page_about_description": "با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته.",
"page_home_metadata_title": "خانه", "page_home_metadata_title": "خانه",
"page_home_metadata_description": "به وب‌سایت رسمی ما خوش آمدید؛ جایی که کیفیت، تجربه و نوآوری در تولید انواع آجر به نمایش گذاشته می‌شود.", "page_home_metadata_description": "به وب‌سایت رسمی ما خوش آمدید؛ جایی که کیفیت، تجربه و نوآوری در تولید انواع آجر به نمایش گذاشته می‌شود.",
@@ -110,6 +111,7 @@
"page_blog_metadata_title": "بلاگ", "page_blog_metadata_title": "بلاگ",
"page_blog_metadata_description": "با مقالات، اخبار و آموزش‌های ما در حوزه آجر و مصالح ساختمانی به‌روز بمانید.", "page_blog_metadata_description": "با مقالات، اخبار و آموزش‌های ما در حوزه آجر و مصالح ساختمانی به‌روز بمانید.",
"page_blog_details_CTA": "جزییات مقاله",
"page_projects_metadata_title": "پروژه‌ها", "page_projects_metadata_title": "پروژه‌ها",
"page_projects_metadata_description": "نگاهی به پروژه‌های موفق ما که با استفاده از آجرهای با کیفیت اجرا شده‌اند.", "page_projects_metadata_description": "نگاهی به پروژه‌های موفق ما که با استفاده از آجرهای با کیفیت اجرا شده‌اند.",
+5 -1
View File
@@ -1,6 +1,8 @@
import images from '@/assets/images/images'; import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner'; import InnerPageBanner from '@/components/layout/innerPages/banner';
import AboutDescription from '@/components/pages/about/AboutDescription';
import AboutTopInfo from '@/components/pages/about/TopInfo'; import AboutTopInfo from '@/components/pages/about/TopInfo';
import HomeStorySection from '@/components/pages/home/HomeStorySection';
import { IPageParams } from '@/models/layout'; import { IPageParams } from '@/models/layout';
import { setRequestLocale } from 'next-intl/server'; import { setRequestLocale } from 'next-intl/server';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
@@ -26,12 +28,14 @@ export default async function AboutUs({
const t = await getTranslations({ locale }); const t = await getTranslations({ locale });
return ( return (
<main className="flex flex-col items-center justify-between"> <main className="mb-10 flex flex-col items-center justify-between">
<InnerPageBanner <InnerPageBanner
title={t('pages_about_title')} title={t('pages_about_title')}
imageSrc={images.aboutBanner.src} imageSrc={images.aboutBanner.src}
/> />
<AboutTopInfo /> <AboutTopInfo />
<AboutDescription className="mb-8 md:mb-14" locale={locale} />
<HomeStorySection />
</main> </main>
); );
} }
+48
View File
@@ -0,0 +1,48 @@
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 ContentGenerator from '@/components/layout/contentGenerator';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import { ILayoutLocaleParams } from '@/models/layout';
import { getPost } from '@/services/blog';
import { getTranslations } from 'next-intl/server';
interface IPageParams extends ILayoutLocaleParams {
id: string;
}
export default async function PostPage({ params }: { params: IPageParams }) {
const paramsData = await params;
const t = await getTranslations({ locale: paramsData.locale });
const { id: postId, locale } = await params;
const routes = routeFactory(t, locale);
const postData = await getPost(postId);
const breadcrumbItems = [
{
...routes.blog,
href: routes.blog.route(),
},
{ title: postData?.title },
] as IBreadcrumbItem[];
return (
<main className="flex flex-col items-center justify-between">
<InnerPageBanner
title={t('pages_blog_title')}
imageSrc={images.blogBanner.src}
/>
<div className="container-fluid mx-auto my-12 md:my-24">
<Breadcrumb items={breadcrumbItems} />
{postData.content && postData.content.length ? (
<ContentGenerator data={postData.content} className="mt-10" />
) : (
<></>
)}
</div>
</main>
);
}
+4 -4
View File
@@ -31,7 +31,7 @@ export default async function Contact({ params }: { params: IPageParams }) {
imageSrc={images.blogBanner.src} imageSrc={images.blogBanner.src}
/> />
<div className="relative flex w-full flex-col bg-gray-200 pt-4 md:pt-28"> <div className="relative flex w-full flex-col bg-gray-200 pt-12 lg:pt-28">
<div className="absolute inset-x-0 inset-y-64 z-2 flex items-end justify-start"> <div className="absolute inset-x-0 inset-y-64 z-2 flex items-end justify-start">
<div className="max-md:hidden md:w-1/2"> <div className="max-md:hidden md:w-1/2">
<Image <Image
@@ -41,9 +41,9 @@ export default async function Contact({ params }: { params: IPageParams }) {
/> />
</div> </div>
</div> </div>
<div className="container mx-auto flex w-full flex-col items-stretch overflow-hidden rounded-lg md:flex-row"> <div className="container mx-auto flex w-full flex-col items-stretch overflow-hidden rounded-lg lg:flex-row">
<TouchUs /> <TouchUs className="p-8 lg:w-1/2" />
<ContactForm /> <ContactForm className="p-4 lg:w-1/2" />
</div> </div>
<div className="mt-4 w-full md:mt-28"> <div className="mt-4 w-full md:mt-28">
+28
View File
@@ -0,0 +1,28 @@
import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';
import { posts } from '../data';
export async function GET(request: Request, context: any) {
const params = await context.params;
const postId = params.id;
const cookieStore = await cookies();
const lang = cookieStore.get('locale')?.value || 'en';
let post = posts.find((post) => post.id === postId);
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
if (lang === 'fa') {
post = {
...post,
title: post.fa_title || post.title,
content: post.fa_content || post.content,
};
}
return NextResponse.json({
data: post,
});
}
+126 -16
View File
@@ -1,59 +1,169 @@
import images from '@/assets/images/images'; import images from '@/assets/images/images';
import { IDynamicContent } from '@/models/dynamicContent';
export interface IPostCardProps { export interface IPostThumbResponseData {
id: string;
title: string;
imageSrc: string;
}
export interface IPostResponseData extends IPostThumbResponseData {
content: IDynamicContent[];
}
export interface IPostData {
id: string; id: string;
title: string; title: string;
fa_title?: string; fa_title?: string;
imageSrc: string; imageSrc: string;
link: string; content: IDynamicContent[];
fa_content: IDynamicContent[];
} }
export const posts: IPostCardProps[] = [ export const posts: IPostData[] = [
{ {
id: '1', id: '1',
title: 'Sustainable Practices Reducing Waste in Industrial Production', title: 'Sustainable Practices Reducing Waste in Industrial Production',
fa_title: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی', fa_title: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
imageSrc: images.blogImg_1.src, imageSrc: '/images/project1.jpeg',
link: '#', content: [
{
type: 'title',
data: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
},
{
type: 'content',
data: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.',
},
{
type: 'bullet',
data: [
'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
'رباتیک پیشرفته و تحول فرآیندهای صنعتی',
'مزایای اصلی استفاده از رباتیک در تولید',
'استفاده از تحلیل داده برای تولید هوشمندتر',
'کاهش هزینه‌ها با اتوماسیون',
'مزایای راه‌حل‌های سفارشی‌سازی در تولید',
],
},
{
type: 'image',
data: {
src: '/images/project2.jpeg',
alt: '',
thumbTitle:
'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.',
},
},
{
type: 'numberList',
data: [
'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
'رباتیک پیشرفته و تحول فرآیندهای صنعتی',
'مزایای اصلی استفاده از رباتیک در تولید',
'استفاده از تحلیل داده برای تولید هوشمندتر',
'کاهش هزینه‌ها با اتوماسیون',
'مزایای راه‌حل‌های سفارشی‌سازی در تولید',
],
},
],
fa_content: [
{
type: 'title',
data: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
},
{
type: 'content',
data: {
links: [
{
link: '/',
key: '_link',
label: 'ایپسوم',
},
],
data: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم\n متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم _link متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده\n از طراحان گرافیک است. لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.',
},
},
{
type: 'bullet',
data: [
'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
'رباتیک پیشرفته و تحول فرآیندهای صنعتی',
'مزایای اصلی استفاده از رباتیک در تولید',
'استفاده از تحلیل داده برای تولید هوشمندتر',
'کاهش هزینه‌ها با اتوماسیون',
'مزایای راه‌حل‌های سفارشی‌سازی در تولید',
],
},
{
type: 'image',
data: {
src: '/images/project1.jpeg',
alt: '',
thumbTitle:
'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.',
},
},
{
type: 'title',
data: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
},
{
type: 'numberList',
data: [
'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی',
'رباتیک پیشرفته و تحول فرآیندهای صنعتی',
'مزایای اصلی استفاده از رباتیک در تولید',
'استفاده از تحلیل داده برای تولید هوشمندتر',
'کاهش هزینه‌ها با اتوماسیون',
'مزایای راه‌حل‌های سفارشی‌سازی در تولید',
],
},
],
}, },
{ {
id: '2', id: '2',
title: 'Advanced Robotics Revolutionizing Industrial Workflows', title: 'Advanced Robotics Revolutionizing Industrial Workflows',
fa_title: 'رباتیک پیشرفته و تحول فرآیندهای صنعتی', fa_title: 'رباتیک پیشرفته و تحول فرآیندهای صنعتی',
imageSrc: images.blogImg_1.src, imageSrc: '/images/project2.jpeg',
link: '#', content: [],
fa_content: [],
}, },
{ {
id: '3', id: '3',
title: 'Top Benefits of the Robotics in Manufacturing', title: 'Top Benefits of the Robotics in Manufacturing',
fa_title: 'مزایای اصلی استفاده از رباتیک در تولید', fa_title: 'مزایای اصلی استفاده از رباتیک در تولید',
imageSrc: images.blogImg_1.src, imageSrc: '/images/project4.jpeg',
link: '#', content: [],
fa_content: [],
}, },
{ {
id: '4', id: '4',
title: 'Leveraging Data Analytics for Smarter Production', title: 'Leveraging Data Analytics for Smarter Production',
fa_title: 'استفاده از تحلیل داده برای تولید هوشمندتر', fa_title: 'استفاده از تحلیل داده برای تولید هوشمندتر',
imageSrc: images.blogImg_1.src, imageSrc: '/images/project3.jpeg',
link: '#', content: [],
fa_content: [],
}, },
{ {
id: '5', id: '5',
title: 'Reducing Operational Costs Through Automation', title: 'Reducing Operational Costs Through Automation',
fa_title: 'کاهش هزینه‌ها با اتوماسیون', fa_title: 'کاهش هزینه‌ها با اتوماسیون',
imageSrc: '/images/post5.jpg', imageSrc: '/images/project2.jpeg',
link: '#', content: [],
fa_content: [],
}, },
{ {
id: '6', id: '6',
title: 'The Advantages of Customized Manufacturing Solutions', title: 'The Advantages of Customized Manufacturing Solutions',
fa_title: 'مزایای راه‌حل‌های سفارشی‌سازی در تولید', fa_title: 'مزایای راه‌حل‌های سفارشی‌سازی در تولید',
imageSrc: '/images/post6.jpg', imageSrc: '/images/project4.jpeg',
link: '#', content: [],
fa_content: [],
}, },
]; ];
export function generatePosts(): IPostCardProps[] { export function generatePosts(): IPostData[] {
return [...posts, ...posts, ...posts].map((e, i) => ({ return [...posts, ...posts, ...posts].map((e, i) => ({
...e, ...e,
id: String(i + 1), id: String(i + 1),
+19 -23
View File
@@ -1,37 +1,33 @@
import { NextResponse } from 'next/server'; import { NextResponse } from 'next/server';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
import { generatePosts, IPostCardProps } from './data'; import { generatePosts, IPostData } from './data';
export async function GET(request: Request) { export async function GET(request: Request) {
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1', 10);
const limit = parseInt(searchParams.get('limit') || '6', 10);
const lang = (await cookies()).get('lang')?.value || 'fa'; const lang = (await cookies()).get('lang')?.value || 'fa';
const page = parseInt(searchParams.get('page') || '1', 10);
const perPage = 10;
let posts = generatePosts(); const posts = generatePosts();
const start = (page - 1) * perPage;
const end = start + perPage;
const paginatedPosts = posts.slice(start, end);
let data: IPostCardProps[] = posts.map( const data = paginatedPosts.map((post) => {
({ id, title, fa_title, imageSrc, link }) => ({ return {
id, id: post.id,
title: lang === 'fa' && fa_title ? fa_title : title, imageSrc: post.imageSrc,
imageSrc, title: post.fa_title || post.title,
link, };
}), });
);
const totalItems = data.length;
const totalPages = Math.ceil(totalItems / limit);
const startIndex = (page - 1) * limit;
const endIndex = startIndex + limit;
const paginatedData = data.slice(startIndex, endIndex);
return NextResponse.json({ return NextResponse.json({
data: paginatedData, data,
pagination: { pagination: {
currentPage: page, page,
totalPages, perPage,
totalItems, total: posts.length,
limit, totalPages: Math.ceil(posts.length / perPage),
}, },
}); });
} }
-3
View File
@@ -7,7 +7,6 @@ const filePath = path.resolve(process.cwd(), 'contactMessages.json');
export async function POST(request: NextRequest) { export async function POST(request: NextRequest) {
try { try {
const body = await request.json(); const body = await request.json();
console.log(body)
const { first_name, last_name, phone, email, message } = body; const { first_name, last_name, phone, email, message } = body;
if (!first_name || !last_name || !phone || !email || !message) { if (!first_name || !last_name || !phone || !email || !message) {
@@ -44,8 +43,6 @@ export async function POST(request: NextRequest) {
message: 'Your message has been received.', message: 'Your message has been received.',
}); });
} catch (error) { } catch (error) {
console.log(error)
console.log(error)
return NextResponse.json( return NextResponse.json(
{ error, message: 'مشکلی پیش آمده.' }, { error, message: 'مشکلی پیش آمده.' },
{ status: 400 }, { status: 400 },
@@ -1,25 +1,30 @@
import type { IProductCardProps } from '@/components/pages/products/productCard/productCard.d'; import type { IProductCardProps } from '@/components/pages/products/productCard/productCard.d';
import { productCategoriesData } from '../../data'; import { productCategoriesData } from '../../data';
import {
ghazaghiCategoryProducts,
ghermezCategoryProducts,
semiromCategoryProducts,
zardCategoryProducts,
} from './productsSeparatedByCode';
const productsRelatedByCategory = {
'1': ghazaghiCategoryProducts,
'2': ghermezCategoryProducts,
'3': zardCategoryProducts,
'40': semiromCategoryProducts,
};
// Helper to generate 50 products for a category // Helper to generate 50 products for a category
export function generateProductsForCategory( export function generateProductsForCategory(
categoryId: string, categoryId: string,
categoryTitle: string,
): IProductCardProps[] { ): IProductCardProps[] {
return Array.from({ length: 50 }, (_, i) => ({ const relatedProducts = (productsRelatedByCategory[categoryId] ||
[]) as IProductCardProps[];
return Array.from(relatedProducts, (related, i) => ({
...related,
id: `${categoryId}-${i + 1}`, id: `${categoryId}-${i + 1}`,
title: `${categoryTitle} Product ${i + 1}`, title: `${related.model}`,
size: `W ${10 + i} * L${20 + i}`, imageSrc: `/images/product${(i % 4) + 1}.png`,
imageSrc: `/images/product${(i % 4) + 1}.png`, // Example image path
// Add other fields from IProductCardProps if needed
})); }));
} }
// Map of categoryId to products
export const productsByCategory: Record<string, IProductCardProps[]> =
Object.fromEntries(
productCategoriesData.map((cat) => [
cat.id,
generateProductsForCategory(cat.id, cat.title),
]),
);
@@ -0,0 +1,461 @@
export const zardCategoryProducts = [
{
model: 'پلاک ۷ معمولی',
color_code: 'زرد',
size: '7 * 33 * 2.5',
categoryId: '3',
},
{
model: 'پلاک ۵.۵ معمولی',
color_code: 'زرد',
size: '5.5 * 26 * 2.5',
categoryId: '3',
},
{
model: 'پلاک ۴ معمولی',
color_code: 'زرد',
size: '4 * 20 * 2.5',
categoryId: '3',
},
{
model: 'پلاک ۵.۵ معمولی',
color_code: 'زرد',
size: '5.5 * 20 * 2.5',
categoryId: '3',
},
{
model: 'پلاک ۵.۵ معمولی',
color_code: 'زرد',
size: '5.5 * 22 * 2.5',
categoryId: '3',
},
{
model: 'ال ۷',
color_code: 'زرد',
size: '7 * 20 * 10 * 2.7',
categoryId: '3',
},
{
model: 'ال ۴',
color_code: 'زرد',
size: '4 * 20 * 10 * 2.7',
categoryId: '3',
},
{
model: 'ال نیمه ۴',
color_code: 'زرد',
size: '4 * 10 * 10 * 2.7',
categoryId: '3',
},
{
model: 'ال نیمه ۵',
color_code: 'زرد',
size: '5.5 * 10 * 10 * 2.7',
categoryId: '3',
},
{
model: 'ال ۵.۵',
color_code: 'زرد',
size: '5.5 * 20 * 10 * 2.7',
categoryId: '3',
},
{
model: 'حصیری ۱۲',
color_code: 'زرد',
size: '4 * 12 * 2.5',
categoryId: '3',
},
{
model: 'دولبگرد',
color_code: 'زرد',
size: '4 * 10 * 2.5',
categoryId: '3',
},
{
model: 'پلاک ۵.۵ معمولی',
color_code: 'زرد',
size: '5.5 * 10 * 2.2',
categoryId: '3',
},
{
model: 'پلاک معمولی ۴',
color_code: 'زرد',
size: '4 * 10 * 2.2',
categoryId: '3',
},
{
model: 'لفتون',
color_code: 'زرد',
size: '10 * 21.5 * 5.5',
categoryId: '3',
},
{
model: 'پلاک ۲۰',
color_code: 'زرد',
size: '20 * 20 * 2.5',
categoryId: '3',
},
{
model: 'گلبرگ',
color_code: 'زرد',
size: '4 * 20 * 2.1',
categoryId: '3',
},
{
model: 'الماسی',
color_code: 'زرد',
size: '4 * 20 * 2.1',
categoryId: '3',
},
];
export const ghermezCategoryProducts = [
{
model: 'لفتون',
color_code: 'قرمز',
size: '10 * 21.5 * 5.5',
categoryId: '2',
},
{
model: 'پلاک ۵.۵',
color_code: 'قرمز',
size: '5.5 * 20 * 2.5',
categoryId: '2',
},
{
model: 'پلاک ۵',
color_code: 'قرمز',
size: '5.5 * 26 * 2.5',
categoryId: '2',
},
{
model: 'پلاک ۷',
color_code: 'قرمز',
size: '7 * 33 * 2.5',
categoryId: '2',
},
{
model: 'ال ۷',
color_code: 'قرمز',
size: '7 * 20 * 10 * 2.7',
categoryId: '2',
},
{
model: 'پلاک',
color_code: 'قرمز',
size: '5.5 * 10 * 2.2',
categoryId: '2',
},
{
model: 'پلاک',
color_code: 'قرمز',
size: '20 * 20 * 2.5',
categoryId: '2',
},
{
model: 'ال ۴',
color_code: 'قرمز',
size: '4 * 10 * 10 * 2.7',
categoryId: '2',
},
{
model: 'ال نیمه ۵',
color_code: 'قرمز',
size: '5.5 * 10 * 10 * 2.7',
categoryId: '2',
},
{
model: 'ال ۵',
color_code: 'قرمز',
size: '5.5 * 20 * 10 * 2.7',
categoryId: '2',
},
];
export const semiromCategoryProducts = [
{
model: 'پافیلی',
color_code: 'سمیرم',
size: '10 * 20 * 2.5',
categoryId: '40',
},
{
model: 'پلاک تایل',
color_code: 'سمیرم در ۴ رنگ',
size: '10 * 50 * 3',
categoryId: '40',
},
{
model: 'پلاک ۵.۵',
color_code: 'سمیرم',
size: '5.5 * 26 * 2.5',
categoryId: '40',
},
{
model: 'پلاک ۷',
color_code: 'سمیرم',
size: '7 * 33 * 2.5',
categoryId: '40',
},
{
model: 'لفتون',
color_code: 'سمیرم',
size: '10 * 21.5 * 5.5',
categoryId: '40',
},
{
model: 'پلاک',
color_code: 'سمیرم',
size: '10 * 20 * 2.5',
categoryId: '40',
},
{
model: 'ال ۵.۵ و ال۷',
color_code: 'سمیرم در ۳ رنگ',
size: '5.5 * 10 * 20 * 2.5',
categoryId: '40',
},
{
model: 'پلاک ۱۰',
color_code: 'سمیرم',
size: '10 * 10 * 2.5',
categoryId: '40',
},
{
model: 'پلاک ۲۰',
color_code: 'سمیرم',
size: '20 * 20 * 2.5',
categoryId: '40',
},
{
model: 'پلاک',
color_code: 'سمیرم در ۳ رنگ',
size: '10 * 20 * 5.5',
categoryId: '40',
},
{
model: 'حلالی',
color_code: 'سمیرم',
size: '5.5 * 20 * 2.5',
categoryId: '40',
},
{
model: 'حصیری',
color_code: 'سمیرم',
size: '5.5 * 20 * 2.5',
categoryId: '40',
},
{
model: 'پلاک ۳',
color_code: 'سمیرم',
size: '3.5 * 33 * 3',
categoryId: '40',
},
{
model: '',
color_code: 'سمیرم',
size: '',
categoryId: '40',
},
{
model: 'دور ستون ۵۰',
color_code: 'سمیرم',
size: '',
categoryId: '40',
},
{
model: 'دور ستون ۴۰',
color_code: 'سمیرم',
size: '',
categoryId: '40',
},
{
model: 'پلاک چهارخونه ۷',
color_code: 'سمیرم',
size: '7 * 33 * 2.5',
categoryId: '40',
},
{
model: 'پلاک شبکه‌ای ۷',
color_code: 'سمیرم',
size: '7 * 33 * 2.5',
categoryId: '40',
},
{
model: 'سر ستون',
color_code: 'سمیرم',
size: '',
categoryId: '40',
},
{
model: 'پلاک مختط ۷',
color_code: 'سمیرم',
size: '7 * 33 * 2.5',
categoryId: '40',
},
{
model: 'پلاک شیاردار ۷',
color_code: 'سمیرم',
size: '7 * 33 * 2.5',
categoryId: '40',
},
{
model: 'پلاک نیلوفر ۱۰',
color_code: 'سمیرم',
size: '10 * 10 * 2.5',
categoryId: '40',
},
{
model: 'پلاک خطی ۱۰',
color_code: 'سمیرم',
size: '10 * 10 * 2.5',
categoryId: '40',
},
{
model: 'دور قاب',
color_code: 'سمیرم',
size: '10 * 20 * 2.5',
categoryId: '40',
},
];
export const shamootiCategoryProducts = [
{
model: 'دورستون ۵۰',
color_code: 'شاموتی',
size: '',
categoryId: '41',
},
{
model: 'پلاک ۱۰',
color_code: 'شاموتی در ۲ رنگ',
size: '10 * 20 * 2.5',
categoryId: '41',
},
{
model: 'پلاک ۲۶',
color_code: 'شاموتی در ۳ رنگ',
size: '5.5 * 26 * 2.5',
categoryId: '41',
},
{
model: 'پلاک ۷',
color_code: 'شاموتی در ۳ رنگ',
size: '7 * 33 * 2.5',
categoryId: '41',
},
{
model: 'پلاک دایره',
color_code: 'شاموتی',
size: '10 * 10 * 2.5',
categoryId: '41',
},
{
model: 'پلاک لب قاشقی',
color_code: 'شاموتی',
size: '10 * 10 * 2.5',
categoryId: '41',
},
{
model: 'دور قاب',
color_code: 'شاموتی',
size: '10 * 20 * 2.5',
categoryId: '41',
},
{
model: 'حلالی',
color_code: 'شاموتی',
size: '5.5 * 20 * 2.5',
categoryId: '41',
},
{
model: 'پلاک ۳',
color_code: 'شاموتی',
size: '3.5 * 33 * 3',
categoryId: '41',
},
{
model: 'پلاک موج',
color_code: 'شاموتی',
size: '10 * 10 * 2.5',
categoryId: '41',
},
{
model: 'ال ۷',
color_code: 'شاموتی',
size: '7 * 20 * 10 * 2.7',
categoryId: '41',
},
{
model: 'ال ۵',
color_code: 'شاموتی',
size: '5.5 * 20 * 10 * 2.7',
categoryId: '41',
},
{
model: 'پاستونی و سرستونی',
color_code: 'شاموتی',
size: '',
categoryId: '41',
},
{
model: 'پلاک ۲۰',
color_code: 'شاموتی',
size: '10 * 20 * 2.5',
categoryId: '41',
},
{
model: 'پلاک ۱۰',
color_code: 'شاموتی',
size: '10 * 10 * 2.5',
categoryId: '41',
},
{
model: 'پلاک معمولی ۲۰',
color_code: 'شاموتی',
size: '20 * 20 * 2.5',
categoryId: '41',
},
{
model: 'دور ستون ۴۰',
color_code: 'شاموتی',
size: '',
categoryId: '41',
},
{
model: 'پلاک ۱۰',
color_code: 'شاموتی در ۲ رنگ',
size: '10 * 20 * 5.5',
categoryId: '41',
},
{
model: 'حصیری',
color_code: 'شاموتی',
size: '5.5 * 20 * 2.5',
categoryId: '41',
},
];
export const sefidCategoryProducts = [
{
model: 'سرستون و پا ستون',
color_code: 'سفید',
size: '',
categoryId: '50',
},
{
model: 'سرستون و پا ستون',
color_code: 'سفید',
size: '',
categoryId: '50',
},
];
export const ghazaghiCategoryProducts = [
{
model: 'پافیلی',
color_code: 'سمیرم',
size: '10 * 20 * 2.5',
categoryId: '40',
},
];
@@ -14,12 +14,11 @@ export async function GET(request: NextRequest, context: any) {
return NextResponse.json({ error: 'Not found' }, { status: 404 }); return NextResponse.json({ error: 'Not found' }, { status: 404 });
} }
// Pagination logic
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1', 10); const page = parseInt(searchParams.get('page') || '1', 10);
const perPage = 20; const perPage = 5;
const allProducts = generateProductsForCategory(category.id, category.title); const allProducts = generateProductsForCategory(category.id);
const start = (page - 1) * perPage; const start = (page - 1) * perPage;
const end = start + perPage; const end = start + perPage;
const paginatedProducts = allProducts.slice(start, end); const paginatedProducts = allProducts.slice(start, end);
+1 -1
View File
@@ -57,7 +57,7 @@ Facing Bricks: Characteristics and Features`,
}, },
{ {
id: '3', id: '3',
title: 'oryZard', title: 'Zard',
fa_title: 'زرد', fa_title: 'زرد',
icon: 'categoryZard', icon: 'categoryZard',
typesCount: 11, typesCount: 11,
-2
View File
@@ -9,8 +9,6 @@ export async function GET(request: Request) {
const cookieStore = await cookies(); const cookieStore = await cookies();
const lang = (await cookieStore).get('locale')?.value || 'en'; const lang = (await cookieStore).get('locale')?.value || 'en';
console.log('!!!!!!!!!!!!!!!!!!!!');
console.log(lang);
if (lang === 'fa') { if (lang === 'fa') {
data = productCategoriesData.map((productCategory) => ({ data = productCategoriesData.map((productCategory) => ({
+3 -2
View File
@@ -11,8 +11,9 @@ export async function GET(request: Request) {
// Pagination logic // Pagination logic
const { searchParams } = new URL(request.url); const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1', 10); const page = parseInt(searchParams.get('page') || '1', 10);
const perPage = 20; const perPage = 6;
const start = (page - 1) * perPage; const start = (page - 1) * perPage;
const end = start + perPage; const end = start + perPage;
@@ -31,7 +32,7 @@ export async function GET(request: Request) {
} }
return NextResponse.json({ return NextResponse.json({
data: projects, data: data,
pagination: { pagination: {
page, page,
perPage, perPage,
+1 -1
View File
@@ -98,7 +98,7 @@ body.font-fa *{
} }
@media (width >=96rem) { @media (width >=96rem) {
max-width: 96rem; max-width: 90vw;
padding-inline: 8rem; padding-inline: 8rem;
} }
} }
@@ -8,6 +8,7 @@ type RouteFactory = (
projects: { title: string; route: () => string }; projects: { title: string; route: () => string };
about: { title: string; route: () => string }; about: { title: string; route: () => string };
blog: { title: string; route: () => string }; blog: { title: string; route: () => string };
singlePost: { title: string; route: (postId: string) => string };
contact: { title: string; route: () => string }; contact: { title: string; route: () => string };
}; };
@@ -36,6 +37,10 @@ const routeFactory: RouteFactory = (t, locale) => ({
title: t('pages_blog_title'), title: t('pages_blog_title'),
route: () => `/${locale}/blog`, route: () => `/${locale}/blog`,
}, },
singlePost: {
title: '',
route: (id: string) => `/${locale}/blog/${id}`,
},
contact: { contact: {
title: t('pages_contact_title'), title: t('pages_contact_title'),
route: () => `/${locale}/contact-us`, route: () => `/${locale}/contact-us`,
@@ -0,0 +1,107 @@
import {
IContentData,
IContentDataLink,
IContentImage,
IDynamicContent,
} from '@/models/dynamicContent';
function ImageContent(data: IContentImage) {
return (
<div className="flex flex-col items-center gap-3">
<img
src={data.src}
alt={data.alt}
className="mx-auto max-h-[60vh] max-w-[90vw] overflow-hidden rounded-2xl object-cover"
/>
{data.thumbTitle ? (
<h5 className="text-center text-sm text-gray-400">{data.thumbTitle}</h5>
) : (
<></>
)}
</div>
);
}
function titleContent(data: string) {
return <h2 className="text-2xl font-bold text-gray-500">{data}</h2>;
}
function fullContent(data: IContentData) {
const splittedData = data.data?.split('\n') || [];
return splittedData.map((e) => {
return (
<p className="indent-5 text-base text-gray-400">
{generateLinkedContent(e, data.links)}
</p>
);
});
}
function generateLinkedContent(data: string, links?: IContentDataLink[]) {
const splitted = data.split(' ');
const mappedLinks = {} as Record<string, IContentDataLink>;
links?.forEach((link) => {
mappedLinks[link.key] = link;
});
return (
<p>
{splitted.map((data) => {
return mappedLinks[data] ? (
<strong>
<a
href={mappedLinks[data].link}
className="text-primary hover:underline"
>
{mappedLinks[data].label}
</a>{' '}
</strong>
) : (
<>{data} </>
);
})}
</p>
);
}
function listContent(data: string[], type: 'bullet' | 'numberList') {
return (
<ul>
{data.map((item, index) => (
<li
key={index}
className={`list-inside ${type === 'bullet' ? 'list-disc' : 'list-decimal'} text-sm text-gray-400`}
>
<h4 className="inline text-sm">{item}</h4>
</li>
))}
</ul>
);
}
export default function ContentGenerator({
data,
className,
}: {
data: IDynamicContent[];
className?: string;
}) {
return (
<div className={`flex flex-col gap-3 md:gap-6 ${className}`}>
{data.map((e) => {
switch (e.type) {
case 'title':
return titleContent(e.data as string);
case 'content':
return <div>{fullContent(e.data as IContentData)}</div>;
case 'image':
return ImageContent(e.data as IContentImage);
case 'bullet':
case 'numberList':
return listContent(e.data as string[], e.type);
}
})}
</div>
);
}
+2 -2
View File
@@ -50,7 +50,7 @@ export default function Navbar() {
/> />
</Link> </Link>
<ul className="hidden space-x-6 text-sm md:flex lg:text-base"> <ul className="hidden space-x-6 text-sm lg:flex lg:text-base">
{links.map(({ route, title }) => ( {links.map(({ route, title }) => (
<li key={route()}> <li key={route()}>
<Link <Link
@@ -71,7 +71,7 @@ export default function Navbar() {
{!open && ( {!open && (
<button <button
className="text-white md:hidden" className="text-white lg:hidden"
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >
<Icon name="menu" className="h-6 w-6" /> <Icon name="menu" className="h-6 w-6" />
@@ -0,0 +1,21 @@
import { TLocales } from '@/models/layout';
import { useTranslations } from 'next-intl';
import { getTranslations } from 'next-intl/server';
export default async function AboutDescription({
locale,
className = '',
}: {
locale: TLocales;
className?: string;
}) {
const t = await getTranslations({ locale });
return (
<div className={`bg-primary w-full ${className}`}>
<div className="container-fluid mx-auto py-20">
<p className="text-xl text-white">{t('page_about_description')}</p>
</div>
</div>
);
}
+32 -17
View File
@@ -6,22 +6,28 @@ import Pagination from '@/components/layout/pagination';
import Image from 'next/image'; import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import { getPosts } from '@/services/blog'; import { getPosts } from '@/services/blog';
import { IPostCardProps } from '@/app/api/blog/data'; import { IPostData } from '@/app/api/blog/data';
import { IPagination } from '@/models/response';
import routeFactory from '@/assets/constants/routeFactory';
import { useParams } from 'next/navigation';
import { TLocales } from '@/models/layout';
import { useTranslations } from 'next-intl';
import Button from '@/components/uikit/button';
export interface IPostsGridProps { export interface IPostsGridProps {
initialPosts: IPostCardProps[]; initialPosts: IPostData[];
initialPagination: { initialPagination: IPagination;
currentPage: number;
totalPages: number;
totalItems: number;
limit: number;
};
} }
export default function PostsGrid({ export default function PostsGrid({
initialPosts, initialPosts,
initialPagination, initialPagination,
}: IPostsGridProps) { }: IPostsGridProps) {
const t = useTranslations();
const { locale } = useParams();
const routes = routeFactory(t, locale as TLocales);
const [posts, setPosts] = useState(initialPosts); const [posts, setPosts] = useState(initialPosts);
const [pagination, setPagination] = useState(initialPagination); const [pagination, setPagination] = useState(initialPagination);
@@ -40,7 +46,10 @@ export default function PostsGrid({
<div className="container mx-auto px-4 py-14 md:py-28"> <div className="container mx-auto px-4 py-14 md:py-28">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((post) => ( {posts.map((post) => (
<div key={post.id} className="overflow-hidden bg-white"> <div
key={post.id}
className="group flex flex-col gap-4 overflow-hidden bg-white md:gap-6"
>
<div className="relative aspect-[1.09] w-full overflow-hidden rounded-4xl"> <div className="relative aspect-[1.09] w-full overflow-hidden rounded-4xl">
<Image <Image
src={post.imageSrc} src={post.imageSrc}
@@ -48,17 +57,23 @@ export default function PostsGrid({
fill fill
className="h-auto w-full object-cover" className="h-auto w-full object-cover"
/> />
</div>
<div className="py-7"> <div className="invisible absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 backdrop-blur-xs transition-all group-hover:visible group-hover:opacity-100">
<h2 className="mb-5 text-xl font-semibold">{post.title}</h2> <Button
<a link={routes.singlePost.route(post.id)}
href={post.link} endIcon="showMore"
className="text-primary text-base hover:underline"
> >
Read More {t('page_blog_details_CTA')}
</a> </Button>
</div> </div>
</div> </div>
<Link
href={routes.singlePost.route(post.id)}
className="group-hover:text-primary text-center text-gray-500"
>
<h2 className="text-xl font-semibold">{post.title}</h2>
</Link>
</div>
))} ))}
</div> </div>
<Pagination {...pagination} onPageChange={fetchPage} /> <Pagination {...pagination} onPageChange={fetchPage} />
@@ -4,10 +4,9 @@ import { useState } from 'react';
import Button from '@/components/uikit/button'; import Button from '@/components/uikit/button';
import FormField from '@/components/uikit/inputs'; import FormField from '@/components/uikit/inputs';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import api from '@/lib/axios';
import { setContact } from '@/services/contact'; import { setContact } from '@/services/contact';
const ContactForm = () => { const ContactForm = ({ className = '' }) => {
const t = useTranslations(); const t = useTranslations();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@@ -57,7 +56,6 @@ const ContactForm = () => {
}; };
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
console.log(e);
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
setContact(formData).finally(() => { setContact(formData).finally(() => {
@@ -68,7 +66,7 @@ const ContactForm = () => {
return ( return (
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="z-10 w-full rounded-4xl bg-white p-4 md:w-1/2" className={`z-10 w-full rounded-4xl bg-white ${className}`}
> >
<h2 className="mt-8 mb-10 text-5xl text-gray-800"> <h2 className="mt-8 mb-10 text-5xl text-gray-800">
{t('com_contact_title')}{' '} {t('com_contact_title')}{' '}
+2 -2
View File
@@ -6,13 +6,13 @@ import { TLocales } from '@/models/layout';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { useParams } from 'next/navigation'; import { useParams } from 'next/navigation';
const TouchUs = () => { const TouchUs = ({ className = '' }) => {
const t = useTranslations(); const t = useTranslations();
const params = useParams(); const params = useParams();
const locale = params.locale as TLocales; const locale = params.locale as TLocales;
return ( return (
<div className="w-full p-8 md:w-1/2"> <div className={`w-full ${className}`}>
<SectionTitle <SectionTitle
title={t('pages_contact_title')} title={t('pages_contact_title')}
description={t('com_contact_touch_title')} description={t('com_contact_touch_title')}
+1 -5
View File
@@ -2,7 +2,6 @@ import SectionTitle from '@/components/layout/sectionTitle';
import { getTranslations } from 'next-intl/server'; import { getTranslations } from 'next-intl/server';
import { TLocales } from '@/models/layout'; import { TLocales } from '@/models/layout';
import { getProjects } from '@/services/projects'; import { getProjects } from '@/services/projects';
import { IProjectCardProps } from '../project/project';
import ProjectsGrid from '../project/ProjectGrid'; import ProjectsGrid from '../project/ProjectGrid';
import Button from '@/components/uikit/button'; import Button from '@/components/uikit/button';
import routeFactory from '@/assets/constants/routeFactory'; import routeFactory from '@/assets/constants/routeFactory';
@@ -12,14 +11,11 @@ interface Props {
export default async function HomePageProjects({ locale }: Props) { export default async function HomePageProjects({ locale }: Props) {
const t = await getTranslations({ locale }); const t = await getTranslations({ locale });
const projects = await getProjects(); const projects = await getProjects();
console.log(projects);
const routes = routeFactory(t, locale); const routes = routeFactory(t, locale);
return ( return (
<div className="w-full"> <div className="w-full">
<div <div className="relative container mx-auto w-full bg-white">
className={`relative container mx-auto w-full bg-white bg-cover bg-fixed bg-center bg-no-repeat pt-28 max-lg:pt-6`}
>
<div className="relative z-20 grid grid-cols-1 lg:grid-cols-2 lg:gap-28"> <div className="relative z-20 grid grid-cols-1 lg:grid-cols-2 lg:gap-28">
<SectionTitle <SectionTitle
title={t('pages_projects_title')} title={t('pages_projects_title')}
+5 -2
View File
@@ -1,5 +1,8 @@
export interface IProductCardProps { export interface IProductCardProps {
imageSrc: string; id: string;
title: string; model: string;
color_code: string;
size: string; size: string;
categoryId: string;
imageSrc: string;
} }
+5 -5
View File
@@ -17,11 +17,11 @@ export default function ProjectsGrid({
return ( return (
<div <div
className={`grid grid-cols-1 gap-8 md:grid-cols-2 md:gap-12 xl:grid-cols-3 xl:gap-18 ${className}`} className={`grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8 xl:grid-cols-3 xl:gap-10 ${className}`}
> >
{projects.map((project, index) => ( {projects.map((project, index) => (
<div className="group flex flex-col gap-4 overflow-hidden bg-white md:gap-6"> <div className="group flex flex-col gap-3 overflow-hidden bg-white md:gap-5">
<div className="relative aspect-[1.4] w-full overflow-hidden rounded-4xl"> <div className="relative aspect-[1.4] w-full overflow-hidden rounded-2xl">
<Image <Image
src={project.imageSrc} src={project.imageSrc}
alt={project.title} alt={project.title}
@@ -38,9 +38,9 @@ export default function ProjectsGrid({
<Link <Link
href={'/'} href={'/'}
key={index} key={index}
className="group-hover:text-primary text-center text-gray-500" className="group-hover:text-primary mb-4 text-center text-gray-500"
> >
<h2 className="text-xl font-semibold">{project.title}</h2> <h2 className="text-base font-medium">{project.title}</h2>
</Link> </Link>
</div> </div>
))} ))}
@@ -14,8 +14,6 @@ export default function ProjectsGridView({
const [pagination, setPagination] = useState(initialPagination); const [pagination, setPagination] = useState(initialPagination);
const fetchPage = (page: number) => { const fetchPage = (page: number) => {
console.log(page);
getProjects(page) getProjects(page)
.then((res) => { .then((res) => {
setProjects(res.data); setProjects(res.data);
+3
View File
@@ -13,8 +13,11 @@ export interface IBaseButtonProps
export interface ITextButtonProps extends IBaseButtonProps { export interface ITextButtonProps extends IBaseButtonProps {
endIcon?: IconName; endIcon?: IconName;
variant?: TButtonVariant;
} }
export interface IIconButtonProps extends IBaseButtonProps { export interface IIconButtonProps extends IBaseButtonProps {
color?: 'primary' | 'secondary'; color?: 'primary' | 'secondary';
size?: 'small' | 'medium' | 'large'; size?: 'small' | 'medium' | 'large';
} }
export type TButtonVariant = 'text' | 'solid';
+22 -9
View File
@@ -1,6 +1,6 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { IIconButtonProps, ITextButtonProps } from './button'; import { IIconButtonProps, ITextButtonProps, TButtonVariant } from './button';
import { Icon } from '../icons'; import { Icon } from '../icons';
import Link from 'next/link'; import Link from 'next/link';
@@ -11,14 +11,20 @@ const Button: React.FC<ITextButtonProps> = ({
endIcon, endIcon,
className, className,
link, link,
variant = 'solid',
onClick, onClick,
...props ...props
}) => { }) => {
const wrapperClass = `group bg-primary relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''}`; const wrapperClass = `group relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''} ${variant === 'solid' ? 'bg-primary' : ''}`;
return link ? ( return link ? (
<Link href={link} className={wrapperClass} {...props}> <Link href={link} className={wrapperClass} {...props}>
<ButtonBody {...props} loading={loading} endIcon={endIcon}> <ButtonBody
{...props}
loading={loading}
endIcon={endIcon}
variant={variant}
>
{children} {children}
</ButtonBody> </ButtonBody>
</Link> </Link>
@@ -29,17 +35,28 @@ const Button: React.FC<ITextButtonProps> = ({
onClick={onClick} onClick={onClick}
className={wrapperClass} className={wrapperClass}
> >
<ButtonBody {...props} loading={loading} endIcon={endIcon}> <ButtonBody
{...props}
loading={loading}
endIcon={endIcon}
variant={variant}
>
{children} {children}
</ButtonBody> </ButtonBody>
</button> </button>
); );
}; };
const variantClassNames = (variant: TButtonVariant) =>
variant === 'solid'
? 'flex items-center justify-center rounded-lg bg-white text-gray-500'
: 'text-primary text-base hover:underline';
const ButtonBody: React.FC<ITextButtonProps> = ({ const ButtonBody: React.FC<ITextButtonProps> = ({
children, children,
loading, loading,
endIcon, endIcon,
variant = 'solid',
}) => ( }) => (
<div className="flex"> <div className="flex">
{loading ? ( {loading ? (
@@ -65,11 +82,7 @@ const ButtonBody: React.FC<ITextButtonProps> = ({
</svg> </svg>
) : null} ) : null}
<div <div className={`px-3 ${variantClassNames(variant)}`}>{children}</div>
className={`flex items-center justify-center rounded-lg bg-white px-3 text-gray-500`}
>
{children}
</div>
{endIcon && !loading ? ( {endIcon && !loading ? (
<span className="flex items-center justify-center p-3"> <span className="flex items-center justify-center p-3">
+30
View File
@@ -0,0 +1,30 @@
export interface IDynamicContent {
type: keyof IDynamicContentItem;
data: string[] | string | IContentImage | IContentData;
}
export interface IDynamicContentItem {
bullet?: string[];
numberList?: string[];
image?: IContentImage;
title?: string;
content?: IContentData;
}
export interface IContentImage {
src: string;
alt: string;
title?: string;
thumbTitle?: string;
}
export interface IContentData {
links?: IContentDataLink[];
data: string;
}
export interface IContentDataLink {
key: string;
link: string;
label: string;
}
+18 -14
View File
@@ -1,33 +1,37 @@
'use server'; 'use server';
import { IPostCardProps } from '@/app/api/blog/data'; import { IPostResponseData, IPostThumbResponseData } from '@/app/api/blog/data';
import api from '@/lib/axios'; import api from '@/lib/axios';
import { IResponse } from '@/models/response';
import { cookies } from 'next/headers'; import { cookies } from 'next/headers';
export interface IResponse<T> {
data: T;
pagination: {
currentPage: number;
totalPages: number;
totalItems: number;
limit: number;
};
}
export async function getPosts( export async function getPosts(
page = 1, page = 1,
limit = 6, ): Promise<IResponse<IPostThumbResponseData[]>> {
): Promise<IResponse<IPostCardProps[]>> {
const cookiesStore = await cookies(); const cookiesStore = await cookies();
return api return api
.get('/blog', { .get('/blog', {
headers: { headers: {
Cookie: cookiesStore.toString(), Cookie: cookiesStore.toString(),
}, },
params: { page, limit }, params: { page },
}) })
.then(({ data }) => { .then(({ data }) => {
return Promise.resolve(data); return Promise.resolve(data);
}) })
.catch((error) => Promise.reject(error)); .catch((error) => Promise.reject(error));
} }
export async function getPost(postId: string): Promise<IPostResponseData> {
const cookiesStore = await cookies();
return api
.get(`/blog/${postId}`, {
headers: {
Cookie: cookiesStore.toString(),
},
})
.then(({ data }) => {
return Promise.resolve(data.data);
})
.catch((error) => Promise.reject(error));
}
+3
View File
@@ -41,6 +41,9 @@ export async function getProducts(
page = 1, page = 1,
): Promise<IResponse<IProductCardProps[]>> { ): Promise<IResponse<IProductCardProps[]>> {
const cookiesStore = await cookies(); const cookiesStore = await cookies();
console.log('request');
console.log(page);
return api return api
.get(`/product_categories/${categoryId}/products`, { .get(`/product_categories/${categoryId}/products`, {
headers: { headers: {
+1 -3
View File
@@ -5,15 +5,13 @@ import { cookies } from 'next/headers';
export async function getProjects(page = 1 as number) { export async function getProjects(page = 1 as number) {
const cookiesStore = await cookies(); const cookiesStore = await cookies();
console.log('page');
console.log(page);
return api return api
.get('/projects', { .get('/projects', {
params: { page },
headers: { headers: {
Cookie: cookiesStore.toString(), Cookie: cookiesStore.toString(),
}, },
params: { page },
}) })
.then(({ data }) => Promise.resolve(data)) .then(({ data }) => Promise.resolve(data))
.catch((error) => Promise.reject(error)); .catch((error) => Promise.reject(error));