From e69e2bf7e123d3ef2e8db9e54a63ff788d6444e1 Mon Sep 17 00:00:00 2001 From: ahasani194 Date: Thu, 19 Jun 2025 15:48:47 +0330 Subject: [PATCH] update project api and page, set pagination in blog and create content generator component, create blog single page, ui fix --- messages/en.d.json.ts | 2 + messages/en.json | 2 + messages/fa.json | 2 + src/app/[locale]/about-us/page.tsx | 6 +- src/app/[locale]/blog/[id]/page.tsx | 48 ++ src/app/[locale]/contact-us/page.tsx | 8 +- src/app/api/blog/[id]/route.ts | 28 ++ src/app/api/blog/data.ts | 142 +++++- src/app/api/blog/route.ts | 42 +- src/app/api/contact/route.ts | 3 - .../product_categories/[id]/products/data.ts | 35 +- .../[id]/products/productsSeparatedByCode.ts | 461 ++++++++++++++++++ .../product_categories/[id]/products/route.ts | 5 +- src/app/api/product_categories/data.ts | 2 +- src/app/api/product_categories/route.ts | 2 - src/app/api/projects/route.ts | 5 +- src/app/globals.css | 2 +- src/assets/constants/routeFactory/index.ts | 5 + .../layout/contentGenerator/index.tsx | 107 ++++ src/components/layout/navbar/index.tsx | 4 +- .../pages/about/AboutDescription.tsx | 21 + src/components/pages/blog/PostsGrid.tsx | 51 +- .../pages/contact/ContactMeForm.tsx | 6 +- src/components/pages/contact/TouchUs.tsx | 4 +- src/components/pages/home/Projects.tsx | 6 +- .../products/productCard/productCard.d.ts | 7 +- src/components/pages/project/ProjectGrid.tsx | 10 +- .../pages/project/projectGridView.tsx | 2 - src/components/uikit/button/button.d.ts | 3 + src/components/uikit/button/index.tsx | 31 +- src/models/dynamicContent.d.ts | 30 ++ src/services/blog.ts | 32 +- src/services/products.ts | 3 + src/services/projects.ts | 4 +- 34 files changed, 984 insertions(+), 137 deletions(-) create mode 100644 src/app/[locale]/blog/[id]/page.tsx create mode 100644 src/app/api/blog/[id]/route.ts create mode 100644 src/app/api/product_categories/[id]/products/productsSeparatedByCode.ts create mode 100644 src/components/layout/contentGenerator/index.tsx create mode 100644 src/components/pages/about/AboutDescription.tsx create mode 100644 src/models/dynamicContent.d.ts diff --git a/messages/en.d.json.ts b/messages/en.d.json.ts index f3cad8e..0c3e36a 100644 --- a/messages/en.d.json.ts +++ b/messages/en.d.json.ts @@ -104,6 +104,7 @@ declare const messages: { "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_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_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_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_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.", diff --git a/messages/en.json b/messages/en.json index 173cf1a..47b2f52 100644 --- a/messages/en.json +++ b/messages/en.json @@ -101,6 +101,7 @@ "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_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_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_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_description": "Discover successful projects completed using our high-quality bricks and expert craftsmanship.", diff --git a/messages/fa.json b/messages/fa.json index 78e6ee6..c8c1f74 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -98,6 +98,7 @@ "page_about_metadata_title": "درباره ما", "page_about_metadata_description": "با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته.", + "page_about_description": "با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته. با ما بیشتر آشنا شوید؛ متعهد به تولید آجرهای باکیفیت با بهره‌گیری از فناوری‌های نوین و روش‌های ساخت پیشرفته.", "page_home_metadata_title": "خانه", "page_home_metadata_description": "به وب‌سایت رسمی ما خوش آمدید؛ جایی که کیفیت، تجربه و نوآوری در تولید انواع آجر به نمایش گذاشته می‌شود.", @@ -110,6 +111,7 @@ "page_blog_metadata_title": "بلاگ", "page_blog_metadata_description": "با مقالات، اخبار و آموزش‌های ما در حوزه آجر و مصالح ساختمانی به‌روز بمانید.", + "page_blog_details_CTA": "جزییات مقاله", "page_projects_metadata_title": "پروژه‌ها", "page_projects_metadata_description": "نگاهی به پروژه‌های موفق ما که با استفاده از آجرهای با کیفیت اجرا شده‌اند.", diff --git a/src/app/[locale]/about-us/page.tsx b/src/app/[locale]/about-us/page.tsx index a6bd762..946bb89 100644 --- a/src/app/[locale]/about-us/page.tsx +++ b/src/app/[locale]/about-us/page.tsx @@ -1,6 +1,8 @@ import images from '@/assets/images/images'; import InnerPageBanner from '@/components/layout/innerPages/banner'; +import AboutDescription from '@/components/pages/about/AboutDescription'; import AboutTopInfo from '@/components/pages/about/TopInfo'; +import HomeStorySection from '@/components/pages/home/HomeStorySection'; import { IPageParams } from '@/models/layout'; import { setRequestLocale } from 'next-intl/server'; import { getTranslations } from 'next-intl/server'; @@ -26,12 +28,14 @@ export default async function AboutUs({ const t = await getTranslations({ locale }); return ( -
+
+ +
); } diff --git a/src/app/[locale]/blog/[id]/page.tsx b/src/app/[locale]/blog/[id]/page.tsx new file mode 100644 index 0000000..d6f03e3 --- /dev/null +++ b/src/app/[locale]/blog/[id]/page.tsx @@ -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 ( +
+ +
+ + {postData.content && postData.content.length ? ( + + ) : ( + <> + )} +
+
+ ); +} diff --git a/src/app/[locale]/contact-us/page.tsx b/src/app/[locale]/contact-us/page.tsx index f0fc27e..cca3f6b 100644 --- a/src/app/[locale]/contact-us/page.tsx +++ b/src/app/[locale]/contact-us/page.tsx @@ -31,7 +31,7 @@ export default async function Contact({ params }: { params: IPageParams }) { imageSrc={images.blogBanner.src} /> -
+
-
- - +
+ +
diff --git a/src/app/api/blog/[id]/route.ts b/src/app/api/blog/[id]/route.ts new file mode 100644 index 0000000..70b9c4a --- /dev/null +++ b/src/app/api/blog/[id]/route.ts @@ -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, + }); +} diff --git a/src/app/api/blog/data.ts b/src/app/api/blog/data.ts index 5943695..9351edd 100644 --- a/src/app/api/blog/data.ts +++ b/src/app/api/blog/data.ts @@ -1,59 +1,169 @@ 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; title: string; fa_title?: string; imageSrc: string; - link: string; + content: IDynamicContent[]; + fa_content: IDynamicContent[]; } -export const posts: IPostCardProps[] = [ +export const posts: IPostData[] = [ { id: '1', title: 'Sustainable Practices Reducing Waste in Industrial Production', fa_title: 'روش‌های پایدار برای کاهش ضایعات در تولید صنعتی', - imageSrc: images.blogImg_1.src, - link: '#', + imageSrc: '/images/project1.jpeg', + 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', title: 'Advanced Robotics Revolutionizing Industrial Workflows', fa_title: 'رباتیک پیشرفته و تحول فرآیندهای صنعتی', - imageSrc: images.blogImg_1.src, - link: '#', + imageSrc: '/images/project2.jpeg', + content: [], + fa_content: [], }, { id: '3', title: 'Top Benefits of the Robotics in Manufacturing', fa_title: 'مزایای اصلی استفاده از رباتیک در تولید', - imageSrc: images.blogImg_1.src, - link: '#', + imageSrc: '/images/project4.jpeg', + content: [], + fa_content: [], }, { id: '4', title: 'Leveraging Data Analytics for Smarter Production', fa_title: 'استفاده از تحلیل داده برای تولید هوشمندتر', - imageSrc: images.blogImg_1.src, - link: '#', + imageSrc: '/images/project3.jpeg', + content: [], + fa_content: [], }, { id: '5', title: 'Reducing Operational Costs Through Automation', fa_title: 'کاهش هزینه‌ها با اتوماسیون', - imageSrc: '/images/post5.jpg', - link: '#', + imageSrc: '/images/project2.jpeg', + content: [], + fa_content: [], }, { id: '6', title: 'The Advantages of Customized Manufacturing Solutions', fa_title: 'مزایای راه‌حل‌های سفارشی‌سازی در تولید', - imageSrc: '/images/post6.jpg', - link: '#', + imageSrc: '/images/project4.jpeg', + content: [], + fa_content: [], }, ]; -export function generatePosts(): IPostCardProps[] { +export function generatePosts(): IPostData[] { return [...posts, ...posts, ...posts].map((e, i) => ({ ...e, id: String(i + 1), diff --git a/src/app/api/blog/route.ts b/src/app/api/blog/route.ts index 41746a9..7f27ad5 100644 --- a/src/app/api/blog/route.ts +++ b/src/app/api/blog/route.ts @@ -1,37 +1,33 @@ import { NextResponse } from 'next/server'; import { cookies } from 'next/headers'; -import { generatePosts, IPostCardProps } from './data'; +import { generatePosts, IPostData } from './data'; export async function GET(request: Request) { 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 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( - ({ id, title, fa_title, imageSrc, link }) => ({ - id, - title: lang === 'fa' && fa_title ? fa_title : title, - imageSrc, - 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); + const data = paginatedPosts.map((post) => { + return { + id: post.id, + imageSrc: post.imageSrc, + title: post.fa_title || post.title, + }; + }); return NextResponse.json({ - data: paginatedData, + data, pagination: { - currentPage: page, - totalPages, - totalItems, - limit, + page, + perPage, + total: posts.length, + totalPages: Math.ceil(posts.length / perPage), }, }); } diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts index d30de19..6afdf1f 100644 --- a/src/app/api/contact/route.ts +++ b/src/app/api/contact/route.ts @@ -7,7 +7,6 @@ const filePath = path.resolve(process.cwd(), 'contactMessages.json'); export async function POST(request: NextRequest) { try { const body = await request.json(); - console.log(body) const { first_name, last_name, phone, email, message } = body; if (!first_name || !last_name || !phone || !email || !message) { @@ -44,8 +43,6 @@ export async function POST(request: NextRequest) { message: 'Your message has been received.', }); } catch (error) { - console.log(error) - console.log(error) return NextResponse.json( { error, message: 'مشکلی پیش آمده.' }, { status: 400 }, diff --git a/src/app/api/product_categories/[id]/products/data.ts b/src/app/api/product_categories/[id]/products/data.ts index 7b76d24..018d73f 100644 --- a/src/app/api/product_categories/[id]/products/data.ts +++ b/src/app/api/product_categories/[id]/products/data.ts @@ -1,25 +1,30 @@ import type { IProductCardProps } from '@/components/pages/products/productCard/productCard.d'; 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 export function generateProductsForCategory( categoryId: string, - categoryTitle: string, ): IProductCardProps[] { - return Array.from({ length: 50 }, (_, i) => ({ + const relatedProducts = (productsRelatedByCategory[categoryId] || + []) as IProductCardProps[]; + + return Array.from(relatedProducts, (related, i) => ({ + ...related, id: `${categoryId}-${i + 1}`, - title: `${categoryTitle} Product ${i + 1}`, - size: `W ${10 + i} * L${20 + i}`, - imageSrc: `/images/product${(i % 4) + 1}.png`, // Example image path - // Add other fields from IProductCardProps if needed + title: `${related.model}`, + imageSrc: `/images/product${(i % 4) + 1}.png`, })); } - -// Map of categoryId to products -export const productsByCategory: Record = - Object.fromEntries( - productCategoriesData.map((cat) => [ - cat.id, - generateProductsForCategory(cat.id, cat.title), - ]), - ); diff --git a/src/app/api/product_categories/[id]/products/productsSeparatedByCode.ts b/src/app/api/product_categories/[id]/products/productsSeparatedByCode.ts new file mode 100644 index 0000000..4eb9fe9 --- /dev/null +++ b/src/app/api/product_categories/[id]/products/productsSeparatedByCode.ts @@ -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', + }, +]; diff --git a/src/app/api/product_categories/[id]/products/route.ts b/src/app/api/product_categories/[id]/products/route.ts index dac8508..3a6932e 100644 --- a/src/app/api/product_categories/[id]/products/route.ts +++ b/src/app/api/product_categories/[id]/products/route.ts @@ -14,12 +14,11 @@ export async function GET(request: NextRequest, context: any) { return NextResponse.json({ error: 'Not found' }, { status: 404 }); } - // Pagination logic const { searchParams } = new URL(request.url); 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 end = start + perPage; const paginatedProducts = allProducts.slice(start, end); diff --git a/src/app/api/product_categories/data.ts b/src/app/api/product_categories/data.ts index 83c0378..14c70a8 100644 --- a/src/app/api/product_categories/data.ts +++ b/src/app/api/product_categories/data.ts @@ -57,7 +57,7 @@ Facing Bricks: Characteristics and Features`, }, { id: '3', - title: 'oryZard', + title: 'Zard', fa_title: 'زرد', icon: 'categoryZard', typesCount: 11, diff --git a/src/app/api/product_categories/route.ts b/src/app/api/product_categories/route.ts index 3a39120..3e8da82 100644 --- a/src/app/api/product_categories/route.ts +++ b/src/app/api/product_categories/route.ts @@ -9,8 +9,6 @@ export async function GET(request: Request) { const cookieStore = await cookies(); const lang = (await cookieStore).get('locale')?.value || 'en'; - console.log('!!!!!!!!!!!!!!!!!!!!'); - console.log(lang); if (lang === 'fa') { data = productCategoriesData.map((productCategory) => ({ diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts index 23a4471..5f6fa05 100644 --- a/src/app/api/projects/route.ts +++ b/src/app/api/projects/route.ts @@ -11,8 +11,9 @@ export async function GET(request: Request) { // Pagination logic const { searchParams } = new URL(request.url); + const page = parseInt(searchParams.get('page') || '1', 10); - const perPage = 20; + const perPage = 6; const start = (page - 1) * perPage; const end = start + perPage; @@ -31,7 +32,7 @@ export async function GET(request: Request) { } return NextResponse.json({ - data: projects, + data: data, pagination: { page, perPage, diff --git a/src/app/globals.css b/src/app/globals.css index 1f3d808..0640e96 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -98,7 +98,7 @@ body.font-fa *{ } @media (width >=96rem) { - max-width: 96rem; + max-width: 90vw; padding-inline: 8rem; } } diff --git a/src/assets/constants/routeFactory/index.ts b/src/assets/constants/routeFactory/index.ts index 4dd2e59..289dcd3 100644 --- a/src/assets/constants/routeFactory/index.ts +++ b/src/assets/constants/routeFactory/index.ts @@ -8,6 +8,7 @@ type RouteFactory = ( projects: { title: string; route: () => string }; about: { title: string; route: () => string }; blog: { title: string; route: () => string }; + singlePost: { title: string; route: (postId: string) => string }; contact: { title: string; route: () => string }; }; @@ -36,6 +37,10 @@ const routeFactory: RouteFactory = (t, locale) => ({ title: t('pages_blog_title'), route: () => `/${locale}/blog`, }, + singlePost: { + title: '', + route: (id: string) => `/${locale}/blog/${id}`, + }, contact: { title: t('pages_contact_title'), route: () => `/${locale}/contact-us`, diff --git a/src/components/layout/contentGenerator/index.tsx b/src/components/layout/contentGenerator/index.tsx new file mode 100644 index 0000000..a863161 --- /dev/null +++ b/src/components/layout/contentGenerator/index.tsx @@ -0,0 +1,107 @@ +import { + IContentData, + IContentDataLink, + IContentImage, + IDynamicContent, +} from '@/models/dynamicContent'; + +function ImageContent(data: IContentImage) { + return ( +
+ {data.alt} + {data.thumbTitle ? ( +
{data.thumbTitle}
+ ) : ( + <> + )} +
+ ); +} + +function titleContent(data: string) { + return

{data}

; +} + +function fullContent(data: IContentData) { + const splittedData = data.data?.split('\n') || []; + return splittedData.map((e) => { + return ( +

+ {generateLinkedContent(e, data.links)} +

+ ); + }); +} + +function generateLinkedContent(data: string, links?: IContentDataLink[]) { + const splitted = data.split(' '); + + const mappedLinks = {} as Record; + links?.forEach((link) => { + mappedLinks[link.key] = link; + }); + + return ( +

+ {splitted.map((data) => { + return mappedLinks[data] ? ( + + + {mappedLinks[data].label} + {' '} + + ) : ( + <>{data} + ); + })} +

+ ); +} + +function listContent(data: string[], type: 'bullet' | 'numberList') { + return ( +
    + {data.map((item, index) => ( +
  • +

    {item}

    +
  • + ))} +
+ ); +} + +export default function ContentGenerator({ + data, + className, +}: { + data: IDynamicContent[]; + className?: string; +}) { + return ( +
+ {data.map((e) => { + switch (e.type) { + case 'title': + return titleContent(e.data as string); + case 'content': + return
{fullContent(e.data as IContentData)}
; + case 'image': + return ImageContent(e.data as IContentImage); + case 'bullet': + case 'numberList': + return listContent(e.data as string[], e.type); + } + })} +
+ ); +} diff --git a/src/components/layout/navbar/index.tsx b/src/components/layout/navbar/index.tsx index 815d16d..4c19a48 100644 --- a/src/components/layout/navbar/index.tsx +++ b/src/components/layout/navbar/index.tsx @@ -50,7 +50,7 @@ export default function Navbar() { /> -
    +
      {links.map(({ route, title }) => (
    • setOpen(true)} > diff --git a/src/components/pages/about/AboutDescription.tsx b/src/components/pages/about/AboutDescription.tsx new file mode 100644 index 0000000..8aad76e --- /dev/null +++ b/src/components/pages/about/AboutDescription.tsx @@ -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 ( +
      +
      +

      {t('page_about_description')}

      +
      +
      + ); +} diff --git a/src/components/pages/blog/PostsGrid.tsx b/src/components/pages/blog/PostsGrid.tsx index b6db168..4697bd2 100644 --- a/src/components/pages/blog/PostsGrid.tsx +++ b/src/components/pages/blog/PostsGrid.tsx @@ -6,22 +6,28 @@ import Pagination from '@/components/layout/pagination'; import Image from 'next/image'; import Link from 'next/link'; 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 { - initialPosts: IPostCardProps[]; - initialPagination: { - currentPage: number; - totalPages: number; - totalItems: number; - limit: number; - }; + initialPosts: IPostData[]; + initialPagination: IPagination; } export default function PostsGrid({ initialPosts, initialPagination, }: IPostsGridProps) { + const t = useTranslations(); + const { locale } = useParams(); + + const routes = routeFactory(t, locale as TLocales); + const [posts, setPosts] = useState(initialPosts); const [pagination, setPagination] = useState(initialPagination); @@ -40,7 +46,10 @@ export default function PostsGrid({
      {posts.map((post) => ( -
      +
      + +
      + +
      -
      -

      {post.title}

      - - Read More → - -
      + +

      {post.title}

      +
      ))}
      diff --git a/src/components/pages/contact/ContactMeForm.tsx b/src/components/pages/contact/ContactMeForm.tsx index 000d5b4..8a04bec 100644 --- a/src/components/pages/contact/ContactMeForm.tsx +++ b/src/components/pages/contact/ContactMeForm.tsx @@ -4,10 +4,9 @@ import { useState } from 'react'; import Button from '@/components/uikit/button'; import FormField from '@/components/uikit/inputs'; import { useTranslations } from 'next-intl'; -import api from '@/lib/axios'; import { setContact } from '@/services/contact'; -const ContactForm = () => { +const ContactForm = ({ className = '' }) => { const t = useTranslations(); const [formData, setFormData] = useState({ @@ -57,7 +56,6 @@ const ContactForm = () => { }; const handleSubmit = (e: React.FormEvent) => { - console.log(e); e.preventDefault(); setLoading(true); setContact(formData).finally(() => { @@ -68,7 +66,7 @@ const ContactForm = () => { return (

      {t('com_contact_title')}{' '} diff --git a/src/components/pages/contact/TouchUs.tsx b/src/components/pages/contact/TouchUs.tsx index 245e28c..10b4bba 100644 --- a/src/components/pages/contact/TouchUs.tsx +++ b/src/components/pages/contact/TouchUs.tsx @@ -6,13 +6,13 @@ import { TLocales } from '@/models/layout'; import { useTranslations } from 'next-intl'; import { useParams } from 'next/navigation'; -const TouchUs = () => { +const TouchUs = ({ className = '' }) => { const t = useTranslations(); const params = useParams(); const locale = params.locale as TLocales; return ( -
      +
      -
      +
      {projects.map((project, index) => ( -
      -
      +
      +
      {project.title} -

      {project.title}

      +

      {project.title}

      ))} diff --git a/src/components/pages/project/projectGridView.tsx b/src/components/pages/project/projectGridView.tsx index b97b654..d1628e4 100644 --- a/src/components/pages/project/projectGridView.tsx +++ b/src/components/pages/project/projectGridView.tsx @@ -14,8 +14,6 @@ export default function ProjectsGridView({ const [pagination, setPagination] = useState(initialPagination); const fetchPage = (page: number) => { - console.log(page); - getProjects(page) .then((res) => { setProjects(res.data); diff --git a/src/components/uikit/button/button.d.ts b/src/components/uikit/button/button.d.ts index 9b7dcc4..70f333d 100644 --- a/src/components/uikit/button/button.d.ts +++ b/src/components/uikit/button/button.d.ts @@ -13,8 +13,11 @@ export interface IBaseButtonProps export interface ITextButtonProps extends IBaseButtonProps { endIcon?: IconName; + variant?: TButtonVariant; } export interface IIconButtonProps extends IBaseButtonProps { color?: 'primary' | 'secondary'; size?: 'small' | 'medium' | 'large'; } + +export type TButtonVariant = 'text' | 'solid'; diff --git a/src/components/uikit/button/index.tsx b/src/components/uikit/button/index.tsx index 1cd5db2..9399367 100644 --- a/src/components/uikit/button/index.tsx +++ b/src/components/uikit/button/index.tsx @@ -1,6 +1,6 @@ 'use client'; import React from 'react'; -import { IIconButtonProps, ITextButtonProps } from './button'; +import { IIconButtonProps, ITextButtonProps, TButtonVariant } from './button'; import { Icon } from '../icons'; import Link from 'next/link'; @@ -11,14 +11,20 @@ const Button: React.FC = ({ endIcon, className, link, + variant = 'solid', onClick, ...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 ? ( - + {children} @@ -29,17 +35,28 @@ const Button: React.FC = ({ onClick={onClick} className={wrapperClass} > - + {children} ); }; +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 = ({ children, loading, endIcon, + variant = 'solid', }) => (
      {loading ? ( @@ -65,11 +82,7 @@ const ButtonBody: React.FC = ({ ) : null} -
      - {children} -
      +
      {children}
      {endIcon && !loading ? ( diff --git a/src/models/dynamicContent.d.ts b/src/models/dynamicContent.d.ts new file mode 100644 index 0000000..1593c38 --- /dev/null +++ b/src/models/dynamicContent.d.ts @@ -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; +} diff --git a/src/services/blog.ts b/src/services/blog.ts index e059a15..d1e9f3c 100644 --- a/src/services/blog.ts +++ b/src/services/blog.ts @@ -1,33 +1,37 @@ 'use server'; -import { IPostCardProps } from '@/app/api/blog/data'; +import { IPostResponseData, IPostThumbResponseData } from '@/app/api/blog/data'; import api from '@/lib/axios'; +import { IResponse } from '@/models/response'; import { cookies } from 'next/headers'; -export interface IResponse { - data: T; - pagination: { - currentPage: number; - totalPages: number; - totalItems: number; - limit: number; - }; -} - export async function getPosts( page = 1, - limit = 6, -): Promise> { +): Promise> { const cookiesStore = await cookies(); return api .get('/blog', { headers: { Cookie: cookiesStore.toString(), }, - params: { page, limit }, + params: { page }, }) .then(({ data }) => { return Promise.resolve(data); }) .catch((error) => Promise.reject(error)); } + +export async function getPost(postId: string): Promise { + 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)); +} diff --git a/src/services/products.ts b/src/services/products.ts index 87820dc..8d8949d 100644 --- a/src/services/products.ts +++ b/src/services/products.ts @@ -41,6 +41,9 @@ export async function getProducts( page = 1, ): Promise> { const cookiesStore = await cookies(); + console.log('request'); + console.log(page); + return api .get(`/product_categories/${categoryId}/products`, { headers: { diff --git a/src/services/projects.ts b/src/services/projects.ts index 0e9f5f7..8c684ac 100644 --- a/src/services/projects.ts +++ b/src/services/projects.ts @@ -5,15 +5,13 @@ import { cookies } from 'next/headers'; export async function getProjects(page = 1 as number) { const cookiesStore = await cookies(); - console.log('page'); - console.log(page); return api .get('/projects', { - params: { page }, headers: { Cookie: cookiesStore.toString(), }, + params: { page }, }) .then(({ data }) => Promise.resolve(data)) .catch((error) => Promise.reject(error));