Merge remote-tracking branch 'origin/main' into navbar
@@ -1,25 +1,19 @@
|
|||||||
# Use official Node.js image as the base
|
# Build stage
|
||||||
FROM node:20-alpine AS builder
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
# Set working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
# Copy the rest of the app
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Build the Next.js app
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
# Set environment variables (optional)
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
ENV PORT=5000
|
||||||
# Expose port
|
COPY --from=builder /app/.next ./.next
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
COPY --from=builder /app/package.json ./package.json
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
# Start the Next.js app
|
|
||||||
CMD ["npm", "start"]
|
CMD ["npm", "start"]
|
||||||
|
|||||||
@@ -1,59 +1,63 @@
|
|||||||
{
|
{
|
||||||
"pages_home_title": "Home",
|
"pages_home_title": "خانه",
|
||||||
"pages_home_description": "Welcome to the home page",
|
"pages_home_description": "به صفحه اصلی خوش آمدید",
|
||||||
"pages_about_title": "About Us",
|
"pages_about_title": "درباره ما",
|
||||||
"pages_about_description": "Learn more about us on this page",
|
"pages_about_description": "در این صفحه بیشتر درباره ما بدانید",
|
||||||
"pages_contact_title": "Contact Us",
|
"pages_contact_title": "تماس با ما",
|
||||||
"pages_product_categories_title": "Products",
|
"pages_product_categories_title": "محصولات",
|
||||||
"pages_products_title": "Products",
|
"pages_products_title": "محصولات",
|
||||||
"pages_blog_title": "Blog",
|
"pages_blog_title": "وبلاگ",
|
||||||
"pages_projects_title": "Projects",
|
"pages_projects_title": "پروژهها",
|
||||||
"pages_contact_description": "Get in touch with us through this page",
|
"pages_contact_description": "از طریق این صفحه با ما در تماس باشید",
|
||||||
"com_home_banner_title": "Excellence innovating",
|
"com_home_banner_title": "نوآوری در تعالی",
|
||||||
"com_home_banner_title_bold": "industry for today",
|
"com_home_banner_title_bold": "صنعت برای امروز",
|
||||||
"com_home_banner_description": "At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes.",
|
"com_home_banner_description": "در قلب فعالیتهای ما تعهد به ارائه محصولات برتر از طریق فناوری پیشرفته و فرآیندهای نوآورانه قرار دارد.",
|
||||||
"com_home_banner_button": " Explore More",
|
"com_home_banner_button": "بیشتر بدانید",
|
||||||
"com_home_Banner_link1": " Advanced Manufacturing Solutions",
|
"com_home_Banner_link1": "راهکارهای پیشرفته تولید",
|
||||||
"com_home_Banner_link2": "Quality Assurance Systems",
|
"com_home_Banner_link2": "سیستمهای تضمین کیفیت",
|
||||||
"com_home_Banner_link3": "State-of-the-Art Technology",
|
"com_home_Banner_link3": "فناوری روز دنیا",
|
||||||
|
|
||||||
"com_about_info_title": "Building quality through",
|
"com_about_info_title": "ساخت کیفیت از طریق",
|
||||||
"com_about_info_title_bold": "industrial innovation",
|
"com_about_info_title_bold": "نوآوری صنعتی",
|
||||||
"com_about_info_description": "At the heart of our operations is a commitment to delivering superior products through cutting-edge technology and innovative processes.",
|
"com_about_info_description": "در قلب فعالیتهای ما تعهد به ارائه محصولات برتر از طریق فناوری پیشرفته و فرآیندهای نوآورانه قرار دارد.",
|
||||||
"com_about_info_years_experience": "years of experience",
|
"com_about_info_years_experience": "سال تجربه",
|
||||||
"com_about_info_achievements_1": "Sustainable Manufacturing",
|
"com_about_info_achievements_1": "تولید پایدار",
|
||||||
"com_about_info_achievements_2": "Advanced Automation",
|
"com_about_info_achievements_2": "اتوماسیون پیشرفته",
|
||||||
"com_about_info_achievements_3": "Efficient Production Processes",
|
"com_about_info_achievements_3": "فرآیندهای تولید کارآمد",
|
||||||
"com_about_info_achievements_4": "Reliable Delivery Services",
|
"com_about_info_achievements_4": "خدمات تحویل قابل اعتماد",
|
||||||
|
|
||||||
"com_home_products_title": "The Qualified bricks for Your Projects",
|
"com_home_products_title": "آجرهای با کیفیت برای پروژههای شما",
|
||||||
"com_home_products_description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
|
"com_home_products_description": "لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است.",
|
||||||
|
|
||||||
"footer_title": "Industrial Solutions for a Modern World",
|
"footer_title": "راهکارهای صنعتی برای دنیای مدرن",
|
||||||
"footer_description": "We are committed to providing personalized industrial solutions that meet the unique needs of our clients. Our team of experts is dedicated to delivering high-quality products and services that drive success in today’s competitive market.",
|
"footer_description": "ما متعهد به ارائه راهکارهای صنعتی شخصیسازی شده هستیم که نیازهای منحصر به فرد مشتریانمان را برآورده میکند. تیم متخصص ما به ارائه محصولات و خدمات با کیفیت بالا که موفقیت را در بازار رقابتی امروز تضمین میکند، متعهد است.",
|
||||||
"footer_slogan": "We are committed to providing personalized industrial solutions.",
|
"footer_slogan": "ما متعهد به ارائه راهکارهای صنعتی شخصیسازی شده هستیم.",
|
||||||
"footer_links_quick_links": "Quick Links",
|
"footer_links_quick_links": "لینکهای سریع",
|
||||||
"footer_links_who_we_are": "Who We Are",
|
"footer_links_who_we_are": "ما که هستیم",
|
||||||
"footer_links_contact": "Contact",
|
"footer_links_contact": "تماس",
|
||||||
"footer_copyright": "Copyright © 2025 All Rights Reserved.",
|
"footer_copyright": "کلیه حقوق محفوظ است © ۲۰۲۵",
|
||||||
"footer_links_address": "123 Industrial Ave, Suite 456, City, Country",
|
"footer_links_address": "خیابان صنعتی ۱۲۳، واحد ۴۵۶، شهر، کشور",
|
||||||
|
|
||||||
"com_home_story_title": "Our Story",
|
"com_home_story_title": "داستان ما",
|
||||||
"com_home_story_slogan": "Building quality through",
|
"com_home_story_slogan": "ساخت کیفیت از طریق",
|
||||||
"com_home_story_slogan_bold": "industrial innovation",
|
"com_home_story_slogan_bold": "نوآوری صنعتی",
|
||||||
"com_home_story_description": "We are a leading provider of industrial solutions, dedicated to delivering high-quality products and services that meet the unique needs of our clients. Our team of experts is committed to driving success in today’s competitive market through innovation and excellence.",
|
"com_home_story_description": "ما ارائهدهنده پیشرو راهکارهای صنعتی هستیم که به ارائه محصولات و خدمات با کیفیت بالا متعهدیم تا نیازهای منحصر به فرد مشتریانمان را برآورده کنیم. تیم متخصص ما با نوآوری و تعالی، موفقیت را در بازار رقابتی امروز تضمین میکند.",
|
||||||
"com_home_story_projects_count": "Completed Projects",
|
"com_home_story_projects_count": "پروژههای انجام شده",
|
||||||
"com_home_story_customer_count": "Happy Customers",
|
"com_home_story_customer_count": "مشتریان راضی",
|
||||||
"com_home_story_experience": "Years of Experience",
|
"com_home_story_experience": "سال تجربه",
|
||||||
|
|
||||||
"com_products_category_Ghazaghi_title": "Ghazaghi",
|
"com_products_category_Ghazaghi_title": "غذاقی",
|
||||||
"com_products_category_Ghermez_title": "Ghermez",
|
"com_products_category_Ghermez_title": "قرمز",
|
||||||
"com_products_category_Semirom_title": "Semirom",
|
"com_products_category_Semirom_title": "سمیرم",
|
||||||
"com_products_category_Zard_title": "Zard",
|
"com_products_category_Zard_title": "زرد",
|
||||||
"com_products_category_types_count": "count Types",
|
"com_products_category_types_count": "تعداد محصولات {count}",
|
||||||
"com_products_category_show_all_CTA": "Show Products",
|
"com_products_category_show_all_CTA": "نمایش محصولات",
|
||||||
"pages_products_content_title": "Brick Classification: Understanding the Variety and Applications",
|
"pages_products_content_title": "طبقهبندی آجر: درک تنوع و کاربردها",
|
||||||
"pages_products_content_description": "Bricks, as one of humanity's oldest and most widely used building materials, have evolved significantly throughout history. Today, they are produced and supplied in an incredibly diverse range, allowing engineers and architects to choose the most suitable brick for any project and need, from traditional and historic structures to modern and advanced buildings. To better understand this diversity, we can classify bricks based on several key criteria, each with its own specific characteristics and applications. \n 1. Classification by Manufacturing Method: From Tradition to Advanced IndustryThe method by which bricks are produced directly impacts their appearance, texture, and properties. Hand-moulded/Stock Bricks: These bricks are produced by manually pressing clay into molds. The result is a brick with a relatively rough texture, uneven edges, and a more traditional appearance, where each piece feels unique. This type of brick is often used for restoration projects or for constructing buildings with a rustic or antique style. Machine-made Bricks: These bricks are produced using industrial machinery, resulting in much higher precision in dimensions and uniformity in appearance. Machine-made bricks themselves are divided into two main categories: Perforated/Hollow Bricks: These bricks have holes, offering several advantages: they reduce building weight, improve thermal and acoustic insulation, and enhance mortar adhesion. Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength. 2. Classification by Raw Material Type and Composition: From Clay to Concrete The material from which a brick is made determines its physical and chemical properties.",
|
"pages_products_content_description": "آجرها به عنوان یکی از قدیمیترین و پرکاربردترین مصالح ساختمانی بشر، در طول تاریخ دچار تحول زیادی شدهاند. امروزه این مصالح در طیف بسیار متنوعی تولید و عرضه میشوند و به مهندسان و معماران این امکان را میدهند تا مناسبترین آجر را برای هر پروژه و نیازی، از سازههای سنتی و تاریخی تا ساختمانهای مدرن و پیشرفته، انتخاب کنند. برای درک بهتر این تنوع، میتوان آجرها را بر اساس چند معیار کلیدی طبقهبندی کرد که هر یک ویژگیها و کاربردهای خاص خود را دارند.\n۱. طبقهبندی بر اساس روش تولید: از سنت تا صنعت پیشرفته\nروش تولید آجرها مستقیماً بر ظاهر، بافت و ویژگیهای آنها تأثیر میگذارد.\nآجرهای دستی/قالبی: این آجرها با فشار دادن دستی خاک رس در قالب تولید میشوند. نتیجه، آجری با بافت نسبتاً زبر، لبههای نامنظم و ظاهری سنتیتر است که هر قطعه حس منحصر به فردی دارد. این نوع آجر اغلب برای پروژههای مرمت یا ساخت ساختمانهایی با سبک روستایی یا قدیمی استفاده میشود.\nآجرهای ماشینی: این آجرها با استفاده از ماشینآلات صنعتی تولید میشوند که دقت ابعادی و یکنواختی ظاهری بسیار بالاتری دارند. آجرهای ماشینی خود به دو دسته اصلی تقسیم میشوند:\nآجرهای سوراخدار/توخالی: این آجرها دارای سوراخهایی هستند که چندین مزیت دارند: کاهش وزن ساختمان، بهبود عایق حرارتی و صوتی و افزایش چسبندگی ملات.\nآجرهای توپر: این آجرها بدون سوراخ بوده و مقاومت فشاری بسیار بالایی دارند. آنها برای سازههای باربر و دیوارهایی که به حداکثر استحکام نیاز دارند، ایدهآل هستند.\n۲. طبقهبندی بر اساس نوع و ترکیب مواد اولیه: از خاک رس تا بتن\nجنس آجر تعیینکننده ویژگیهای فیزیکی و شیمیایی آن است.",
|
||||||
|
|
||||||
"com_products_products_category_title": "Products of {title} Category"
|
"com_products_products_category_title": "محصولات دسته {title}",
|
||||||
|
|
||||||
|
"pages_notFound_title": "صفحه ۴۰۴ پیدا نشد",
|
||||||
|
"pages_notFound_description": "صفحهای که به دنبال آن هستید وجود ندارد یا منتقل شده است. لطفاً آدرس را بررسی کنید یا به صفحه اصلی بازگردید.",
|
||||||
|
"pages_notFound_button": "بازگشت به صفحه اصلی"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
import type { NextRequest } from 'next/server';
|
|
||||||
|
|
||||||
const PUBLIC_FILE = /\.(.*)$/;
|
|
||||||
const locales = ['en', 'fa'];
|
|
||||||
const defaultLocale = 'en';
|
|
||||||
|
|
||||||
export function middleware(request: NextRequest) {
|
|
||||||
const { pathname } = request.nextUrl;
|
|
||||||
|
|
||||||
// Ignore public files and API routes
|
|
||||||
if (
|
|
||||||
pathname.startsWith('/api') ||
|
|
||||||
PUBLIC_FILE.test(pathname)
|
|
||||||
) {
|
|
||||||
return NextResponse.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the pathname already includes a locale
|
|
||||||
const pathnameIsMissingLocale = locales.every(
|
|
||||||
(locale) => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (pathnameIsMissingLocale) {
|
|
||||||
// Redirect to default locale
|
|
||||||
return NextResponse.redirect(
|
|
||||||
new URL(`/${defaultLocale}${pathname}`, request.url)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: [
|
|
||||||
/*
|
|
||||||
Match all request paths except for:
|
|
||||||
- static files (/_next/static/)
|
|
||||||
- API routes (/api/)
|
|
||||||
*/
|
|
||||||
'/((?!_next/static|_next/image|favicon.ico|api).*)',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module.exports = {
|
||||||
|
siteUrl: process.env.SITE_URL || 'http://localhost:3000',
|
||||||
|
generateRobotsTxt: true,
|
||||||
|
changefreq: 'weekly',
|
||||||
|
priority: 0.7,
|
||||||
|
sitemapSize: 7000,
|
||||||
|
// Enable i18n support
|
||||||
|
i18n: {
|
||||||
|
locales: ['en', 'fa'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
},
|
||||||
|
// Optionally, exclude certain paths (like API routes)
|
||||||
|
exclude: ['/api/*', '/_next/*'],
|
||||||
|
// If you want to include additional sitemaps (for subdirectories, etc.)
|
||||||
|
// additionalSitemaps: [
|
||||||
|
// `${process.env.SITE_URL || 'http://localhost:5000'}/custom-sitemap.xml`,
|
||||||
|
// ],
|
||||||
|
};
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/leaflet": "^1.9.18",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -27,6 +28,7 @@
|
|||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-prettier": "^5.4.1",
|
"eslint-plugin-prettier": "^5.4.1",
|
||||||
|
"next-sitemap": "^4.2.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.12",
|
"prettier-plugin-tailwindcss": "^0.6.12",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
@@ -58,6 +60,12 @@
|
|||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@corex/deepmerge": {
|
||||||
|
"version": "4.0.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/@corex/deepmerge/-/deepmerge-4.0.43.tgz",
|
||||||
|
"integrity": "sha512-N8uEMrMPL0cu/bdboEWpQYb/0i2K5Qn8eCsxzOmxSggJbbQte7ljMRoXm917AbntqTGOzdTu+vP3KOOzoC70HQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@emnapi/core": {
|
"node_modules/@emnapi/core": {
|
||||||
"version": "1.4.3",
|
"version": "1.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.4.3.tgz",
|
||||||
@@ -1289,6 +1297,12 @@
|
|||||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/geojson": {
|
||||||
|
"version": "7946.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
|
||||||
|
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.15",
|
"version": "7.0.15",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
|
||||||
@@ -1301,6 +1315,15 @@
|
|||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/leaflet": {
|
||||||
|
"version": "1.9.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz",
|
||||||
|
"integrity": "sha512-ht2vsoPjezor5Pmzi5hdsA7F++v5UGq9OlUduWHmMZiuQGIpJ2WS5+Gg9HaAA79gNh1AIPtCqhzejcIZ3lPzXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/geojson": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.17.57",
|
"version": "20.17.57",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz",
|
||||||
@@ -4638,6 +4661,39 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-sitemap": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-sitemap/-/next-sitemap-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-vjdCxeDuWDzldhCnyFCQipw5bfpl4HmZA7uoo3GAaYGjGgfL4Cxb1CiztPuWGmS+auYs7/8OekRS8C2cjdAsjQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/iamvishnusankar/next-sitemap.git"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@corex/deepmerge": "^4.0.43",
|
||||||
|
"@next/env": "^13.4.3",
|
||||||
|
"fast-glob": "^3.2.12",
|
||||||
|
"minimist": "^1.2.8"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"next-sitemap": "bin/next-sitemap.mjs",
|
||||||
|
"next-sitemap-cjs": "bin/next-sitemap.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/next-sitemap/node_modules/@next/env": {
|
||||||
|
"version": "13.5.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.11.tgz",
|
||||||
|
"integrity": "sha512-fbb2C7HChgM7CemdCY+y3N1n8pcTKdqtQLbC7/EQtPdLvlMUT9JX/dBYl8MMZAtYG4uVMyPFHXckb68q/NRwqg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/next/node_modules/postcss": {
|
"node_modules/next/node_modules/postcss": {
|
||||||
"version": "8.4.31",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"sitemap": "next-sitemap"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
@@ -20,6 +21,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
"@tailwindcss/postcss": "^4",
|
"@tailwindcss/postcss": "^4",
|
||||||
|
"@types/leaflet": "^1.9.18",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^19",
|
"@types/react": "^19",
|
||||||
"@types/react-dom": "^19",
|
"@types/react-dom": "^19",
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
"eslint-config-prettier": "^10.1.5",
|
"eslint-config-prettier": "^10.1.5",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"eslint-plugin-prettier": "^5.4.1",
|
"eslint-plugin-prettier": "^5.4.1",
|
||||||
|
"next-sitemap": "^4.2.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.12",
|
"prettier-plugin-tailwindcss": "^0.6.12",
|
||||||
"tailwindcss": "^4",
|
"tailwindcss": "^4",
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 578 KiB |
|
After Width: | Height: | Size: 156 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
# *
|
||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
# Host
|
||||||
|
Host: http://localhost:3000
|
||||||
|
|
||||||
|
# Sitemaps
|
||||||
|
Sitemap: http://localhost:3000/sitemap.xml
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
</sitemapindex>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import '@/app/globals.css';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import type { IPageParams } from '@/models/layout.d';
|
||||||
|
|
||||||
|
// export const metadata: Metadata = {
|
||||||
|
// title: 'Create Next App',
|
||||||
|
// description: 'Generated by create next app',
|
||||||
|
// };
|
||||||
|
|
||||||
|
export default async function ErrorLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: IPageParams;
|
||||||
|
}>) {
|
||||||
|
const paramsData = await params;
|
||||||
|
const locale = paramsData.locale;
|
||||||
|
const fontClass = locale === 'fa' ? 'font-fa' : 'font-en';
|
||||||
|
let messages;
|
||||||
|
try {
|
||||||
|
messages = (await import(`../../../messages/${locale}.json`)).default;
|
||||||
|
} catch (error) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
|
||||||
|
<body className={`min-h-svh w-full overflow-x-hidden ${fontClass}`}>
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
{children}
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,19 +1,16 @@
|
|||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import Button from '@/components/uikit/button';
|
import Button from '@/components/uikit/button';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function NotFound({
|
export default async function NotFound({ params }: { params: IPageParams }) {
|
||||||
params,
|
const paramsData = await params;
|
||||||
}: {
|
const t = await getTranslations({ locale: paramsData.locale });
|
||||||
params: ILayoutLocaleParams;
|
const routes = routeFactory(t, paramsData.locale);
|
||||||
}) {
|
|
||||||
const t = await getTranslations({ locale: params.locale });
|
|
||||||
const routes = routeFactory(t, params.locale);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto my-32 flex flex-col items-center justify-center gap-6">
|
<div className="container mx-auto flex h-svh flex-col items-center justify-center gap-6">
|
||||||
<h2 className="text-center text-4xl md:text-[5rem]">
|
<h2 className="text-center text-4xl md:text-[5rem]">
|
||||||
{t('pages_notFound_title')}
|
{t('pages_notFound_title')}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
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 AboutTopInfo from '@/components/pages/about/TopInfo';
|
import AboutTopInfo from '@/components/pages/about/TopInfo';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { useTranslations } from 'next-intl';
|
|
||||||
import { setRequestLocale } from 'next-intl/server';
|
import { setRequestLocale } from 'next-intl/server';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function AboutUs({
|
export default async function AboutUs({
|
||||||
params,
|
params,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
params: ILayoutLocaleParams;
|
params: IPageParams;
|
||||||
}>) {
|
}>) {
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
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 PostsGrid from '@/components/pages/blog/PostsGrid';
|
import PostsGrid from '@/components/pages/blog/PostsGrid';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function Blog({
|
export default async function Blog({ params }: { params: IPageParams }) {
|
||||||
params,
|
const paramsData = await params;
|
||||||
}: {
|
const t = await getTranslations({ locale: paramsData.locale });
|
||||||
params: ILayoutLocaleParams;
|
|
||||||
}) {
|
|
||||||
const t = await getTranslations({ locale: params.locale });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import ContactForm from '@/components/pages/contact/ContactMeForm';
|
|||||||
import TouchUs from '@/components/pages/contact/TouchUs';
|
import TouchUs from '@/components/pages/contact/TouchUs';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
|
|
||||||
export default async function Contact({
|
export default async function Contact({ params }: { params: IPageParams }) {
|
||||||
params,
|
const { locale } = await params;
|
||||||
}: {
|
|
||||||
params: ILayoutLocaleParams;
|
const t = await getTranslations({ locale });
|
||||||
}) {
|
|
||||||
const t = await getTranslations({ locale: params.locale });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
|
|||||||
@@ -3,36 +3,30 @@ import '@/app/globals.css';
|
|||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { NextIntlClientProvider } from 'next-intl';
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
import type { ILayoutLocaleParams } from '@/models/layout.d';
|
import type { IPageParams } from '@/models/layout.d';
|
||||||
|
|
||||||
// export const metadata: Metadata = {
|
|
||||||
// title: 'Create Next App',
|
|
||||||
// description: 'Generated by create next app',
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
export default async function RootLayout({
|
||||||
children,
|
children,
|
||||||
params,
|
params,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: ILayoutLocaleParams;
|
params: IPageParams;
|
||||||
}>) {
|
}>) {
|
||||||
const locale = params.locale;
|
const paramsData = await params;
|
||||||
console.log(locale);
|
const locale = paramsData.locale;
|
||||||
|
|
||||||
|
const fontClass = locale === 'fa' ? 'font-fa' : 'font-en';
|
||||||
|
|
||||||
let messages;
|
let messages;
|
||||||
try {
|
try {
|
||||||
console.log(await import(`../../../messages/en.json`));
|
messages = (await import(`../../../messages/${locale}.json`)).default;
|
||||||
messages = (await import(`../../../messages/en.json`)).default;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
|
||||||
|
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
|
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
|
||||||
<body className="min-h-svh w-full overflow-x-hidden">
|
<body className={`min-h-svh w-full overflow-x-hidden ${fontClass}`}>
|
||||||
<NextIntlClientProvider locale={locale} messages={messages}>
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
{children}
|
{children}
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
@@ -2,17 +2,13 @@ import Header from '@/components/layout/header';
|
|||||||
import AboutTopInfo from '@/components/pages/about/TopInfo';
|
import AboutTopInfo from '@/components/pages/about/TopInfo';
|
||||||
import HomeStorySection from '@/components/pages/home/HomeStorySection';
|
import HomeStorySection from '@/components/pages/home/HomeStorySection';
|
||||||
import HomePageProducts from '@/components/pages/home/Products';
|
import HomePageProducts from '@/components/pages/home/Products';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import type { IPageParams } from '@/models/layout.d';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
interface IPageParams {
|
export default async function Home({ params }: { params: IPageParams }) {
|
||||||
params: ILayoutLocaleParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function Home({ params }: IPageParams) {
|
|
||||||
const { locale } = await params;
|
const { locale } = await params;
|
||||||
|
|
||||||
const t = await getTranslations({ locale });
|
await getTranslations({ locale });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ import Breadcrumb from '@/components/layout/breadcrumb';
|
|||||||
import { IBreadcrumbItem } from '@/components/layout/breadcrumb/breadcrumb';
|
import { IBreadcrumbItem } from '@/components/layout/breadcrumb/breadcrumb';
|
||||||
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
||||||
import ProductCategoryCardInProducts from '@/components/pages/productCategories/ProductCategoryCardInProducts';
|
import ProductCategoryCardInProducts from '@/components/pages/productCategories/ProductCategoryCardInProducts';
|
||||||
import ProductCard from '@/components/pages/products/ProductCard';
|
import ProductList from '@/components/pages/products/productList';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { getProductCategoryById, getProducts } from '@/services/products';
|
import { getProductCategoryById, getProducts } from '@/services/products';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
interface IParams extends ILayoutLocaleParams {
|
interface IParams {
|
||||||
product_category_id: string;
|
product_category_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ProductPage({
|
export default async function ProductPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: Promise<IParams>;
|
params: IPageParams<IParams>;
|
||||||
}) {
|
}) {
|
||||||
const { product_category_id: productCategoryId, locale } = await params;
|
const { product_category_id: productCategoryId, locale } = await params;
|
||||||
const t = await getTranslations({ locale });
|
const t = await getTranslations({ locale });
|
||||||
@@ -33,6 +33,8 @@ export default async function ProductPage({
|
|||||||
{ title: productCategory.title },
|
{ title: productCategory.title },
|
||||||
] as IBreadcrumbItem[];
|
] as IBreadcrumbItem[];
|
||||||
|
|
||||||
|
const refetch = () => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="">
|
<main className="">
|
||||||
<InnerPageBanner
|
<InnerPageBanner
|
||||||
@@ -49,11 +51,11 @@ export default async function ProductPage({
|
|||||||
<h2 className="mb-6 text-3xl font-semibold text-gray-500">
|
<h2 className="mb-6 text-3xl font-semibold text-gray-500">
|
||||||
{productCategory.title} Products
|
{productCategory.title} Products
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
<ProductList
|
||||||
{products.map((product, index) => (
|
initialProducts={products.data}
|
||||||
<ProductCard {...product} key={index} />
|
initialPagination={products.pagination}
|
||||||
))}
|
categoryId={productCategoryId}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ import images from '@/assets/images/images';
|
|||||||
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
||||||
import ProductsCategoryCard from '@/components/pages/productCategories/ProductCategoryCard';
|
import ProductsCategoryCard from '@/components/pages/productCategories/ProductCategoryCard';
|
||||||
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
|
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { getProductCategories } from '@/services/products';
|
import { getProductCategories } from '@/services/products';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function ProductsPage({
|
export default async function ProductsPage({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
params: ILayoutLocaleParams;
|
params: IPageParams;
|
||||||
}) {
|
}) {
|
||||||
const awaitedParams = await params;
|
const { locale } = await params;
|
||||||
const t = await getTranslations({ locale: awaitedParams.locale });
|
const t = await getTranslations({ locale });
|
||||||
const productCategories: IProductCategoryCardProps[] =
|
const productCategories: IProductCategoryCardProps[] =
|
||||||
await getProductCategories();
|
await getProductCategories();
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
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 ProjectsGrid from '@/components/pages/project/ProjectGrid';
|
import ProjectsGrid from '@/components/pages/project/ProjectGrid';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { IPageParams } from '@/models/layout';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function Projects({
|
export default async function Projects({ params }: { params: IPageParams }) {
|
||||||
params,
|
const { locale } = await params;
|
||||||
}: {
|
const t = await getTranslations({ locale });
|
||||||
params: ILayoutLocaleParams;
|
|
||||||
}) {
|
|
||||||
const t = await getTranslations({ locale: params.locale });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { productCategories } from '../../data';
|
||||||
|
import type { IProductCardProps } from '@/components/pages/products/productCard/productCard.d';
|
||||||
|
|
||||||
|
// Helper to generate 50 products for a category
|
||||||
|
export function generateProductsForCategory(
|
||||||
|
categoryId: string,
|
||||||
|
categoryTitle: string,
|
||||||
|
): IProductCardProps[] {
|
||||||
|
return Array.from({ length: 50 }, (_, i) => ({
|
||||||
|
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
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map of categoryId to products
|
||||||
|
export const productsByCategory: Record<string, IProductCardProps[]> =
|
||||||
|
Object.fromEntries(
|
||||||
|
productCategories.map((cat) => [
|
||||||
|
cat.id,
|
||||||
|
generateProductsForCategory(cat.id, cat.title),
|
||||||
|
]),
|
||||||
|
);
|
||||||
@@ -1,29 +1,36 @@
|
|||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from 'next/server';
|
||||||
// import type { NextApiRequestContext } from 'next';
|
|
||||||
|
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { productCategories } from '../../data';
|
import { productCategories } from '../../data';
|
||||||
import images from '@/assets/images/images';
|
import { generateProductsForCategory } from './data';
|
||||||
|
|
||||||
export async function GET(request: NextRequest, context: any) {
|
export async function GET(request: NextRequest, context: any) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const lang = cookieStore.get('lang')?.value || 'en';
|
const lang = cookieStore.get('locale')?.value || 'en';
|
||||||
|
|
||||||
const category = productCategories.find(
|
const params = await context.params;
|
||||||
(cat) => cat.id === context.params.id,
|
const category = productCategories.find((cat) => cat.id === params.id);
|
||||||
);
|
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
const product = {
|
|
||||||
title: 'Product title',
|
// Pagination logic
|
||||||
size: 'W 18 * L10',
|
const { searchParams } = new URL(request.url);
|
||||||
imageSrc: '/product1.png',
|
const page = parseInt(searchParams.get('page') || '1', 10);
|
||||||
};
|
const perPage = 20;
|
||||||
const products = Array.from({ length: 10 }, () => product);
|
|
||||||
|
const allProducts = generateProductsForCategory(category.id, category.title);
|
||||||
|
const start = (page - 1) * perPage;
|
||||||
|
const end = start + perPage;
|
||||||
|
const paginatedProducts = allProducts.slice(start, end);
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
data: products,
|
data: paginatedProducts,
|
||||||
|
pagination: {
|
||||||
|
page,
|
||||||
|
perPage,
|
||||||
|
total: allProducts.length,
|
||||||
|
totalPages: Math.ceil(allProducts.length / perPage),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,42 @@
|
|||||||
import { NextResponse, NextRequest } from 'next/server';
|
'use server';
|
||||||
// import type { NextApiRequestContext } from 'next';
|
|
||||||
|
|
||||||
|
import { NextResponse, NextRequest } from 'next/server';
|
||||||
import { cookies } from 'next/headers';
|
import { cookies } from 'next/headers';
|
||||||
import { productCategories } from '../data';
|
import { productCategories } from '../data';
|
||||||
|
|
||||||
|
interface IContext {
|
||||||
|
params: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IParams {}
|
||||||
|
|
||||||
export async function GET(request: NextRequest, context: any) {
|
export async function GET(request: NextRequest, context: any) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const lang = cookieStore.get('lang')?.value || 'en';
|
const lang = cookieStore.get('locale')?.value || 'en';
|
||||||
|
|
||||||
const category = productCategories.find(
|
const params = (await context).params;
|
||||||
(cat) => cat.id === context.params.id,
|
|
||||||
);
|
const { id } = params;
|
||||||
|
|
||||||
|
let category = productCategories.find((cat) => cat.id === id);
|
||||||
|
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const titleData =
|
if (lang === 'fa') {
|
||||||
lang === 'fa' && category.fa_title ? category.fa_title : category.title;
|
category = {
|
||||||
const priceTypeTitleData =
|
...category,
|
||||||
lang === 'fa' && category.fa_priceTypeTitle
|
title: category.fa_title || category.title,
|
||||||
? category.fa_priceTypeTitle
|
priceTypeTitle: category.fa_priceTypeTitle || category.priceTypeTitle,
|
||||||
: category.priceTypeTitle;
|
bestForTitle: category.fa_bestForTitle || category.bestForTitle,
|
||||||
const bestForTitleData =
|
description: category.fa_description || category.description,
|
||||||
lang === 'fa' && category.fa_bestForTitle
|
};
|
||||||
? category.fa_bestForTitle
|
}
|
||||||
: category.bestForTitle;
|
|
||||||
const descriptionData =
|
|
||||||
lang === 'fa' && category.fa_description
|
|
||||||
? category.fa_description
|
|
||||||
: category.description;
|
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
data: {
|
data: category,
|
||||||
id: category.id,
|
|
||||||
icon: category.icon,
|
|
||||||
typesCount: category.typesCount,
|
|
||||||
title: titleData,
|
|
||||||
priceTypeTitle: priceTypeTitleData,
|
|
||||||
bestForTitle: bestForTitleData,
|
|
||||||
description: descriptionData,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { writeFile, readFile } from 'fs/promises';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const filePath = path.resolve(process.cwd(), 'contactMessages.json');
|
||||||
|
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
const body = await request.json();
|
||||||
|
const { first_name, last_name, phone, email, message } = body;
|
||||||
|
|
||||||
|
if (!first_name || !last_name || !phone || !email || !message) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'مقدار تمامی فیلدها را پر کنید' },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let messages = [];
|
||||||
|
try {
|
||||||
|
const fileData = await readFile(filePath, 'utf-8');
|
||||||
|
messages = JSON.parse(fileData);
|
||||||
|
} catch {
|
||||||
|
messages = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const newMessage = {
|
||||||
|
name,
|
||||||
|
email,
|
||||||
|
message,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
messages.push(newMessage);
|
||||||
|
|
||||||
|
// Write updated messages back to file
|
||||||
|
await writeFile(filePath, JSON.stringify(messages, null, 2), 'utf-8');
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Your message has been received.',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error, message: 'مشکلی پیش آمده.' },
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,23 +23,25 @@ export async function GET(request: Request) {
|
|||||||
description,
|
description,
|
||||||
fa_description,
|
fa_description,
|
||||||
}) => {
|
}) => {
|
||||||
const titleData = lang === 'fa' && fa_title ? fa_title : title;
|
if (lang === 'fa') {
|
||||||
const priceTypeTitleData =
|
return {
|
||||||
lang === 'fa' && fa_priceTypeTitle
|
id,
|
||||||
? fa_priceTypeTitle
|
icon,
|
||||||
: priceTypeTitle;
|
typesCount,
|
||||||
const bestForTitleData =
|
title: fa_title || title,
|
||||||
lang === 'fa' && fa_bestForTitle ? fa_bestForTitle : bestForTitle;
|
priceTypeTitle: fa_priceTypeTitle || priceTypeTitle,
|
||||||
const descriptionData =
|
bestForTitle: fa_bestForTitle || bestForTitle,
|
||||||
lang === 'fa' && fa_description ? fa_description : description;
|
description: fa_description || description,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
icon,
|
icon,
|
||||||
typesCount,
|
typesCount,
|
||||||
title: titleData,
|
title,
|
||||||
priceTypeTitle: priceTypeTitleData,
|
priceTypeTitle,
|
||||||
bestForTitle: bestForTitleData,
|
bestForTitle,
|
||||||
description: descriptionData,
|
description,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
import { cookies } from 'next/headers';
|
|
||||||
import { productCategories } from '../product_categories/data';
|
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
|
||||||
const data = productCategories.find(
|
|
||||||
(productCategory) => productCategory.id === request.url.split('/').pop(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const cookieStore = cookies();
|
|
||||||
const lang = (await cookieStore).get('lang')?.value || 'fa';
|
|
||||||
|
|
||||||
// if (lang === 'fa') {
|
|
||||||
// data = productCategories.find(
|
|
||||||
// ({
|
|
||||||
// id,
|
|
||||||
// title,
|
|
||||||
// fa_title,
|
|
||||||
// icon,
|
|
||||||
// typesCount,
|
|
||||||
// priceTypeTitle,
|
|
||||||
// fa_priceTypeTitle,
|
|
||||||
// bestForTitle,
|
|
||||||
// fa_bestForTitle,
|
|
||||||
// }) => {
|
|
||||||
// const titleData = lang === 'fa' && fa_title ? fa_title : title;
|
|
||||||
// const priceTypeTitleData =
|
|
||||||
// lang === 'fa' && fa_priceTypeTitle
|
|
||||||
// ? fa_priceTypeTitle
|
|
||||||
// : priceTypeTitle;
|
|
||||||
// const bestForTitleData =
|
|
||||||
// lang === 'fa' && fa_bestForTitle ? fa_bestForTitle : bestForTitle;
|
|
||||||
// return {data}
|
|
||||||
// }
|
|
||||||
|
|
||||||
return NextResponse.json({ data });
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
|
||||||
const data = await request.json();
|
|
||||||
return NextResponse.json({ message: 'Received POST request', data });
|
|
||||||
}
|
|
||||||
@@ -1,116 +1,16 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tailwindcss/utilities";
|
@import "tailwindcss/utilities";
|
||||||
/* manrope-200 - latin */
|
@import "../assets/fonts/manrope/manrope.css";
|
||||||
@font-face {
|
@import "../assets/fonts/vazir/vazir.css";
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 200;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-200.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-200.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-200.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-200.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-200.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-200.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-300 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 300;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-300.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-300.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-300.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-300.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-300.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-300.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-regular - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-regular.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-regular.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-regular.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-regular.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-regular.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-regular.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-500 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-500.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-500.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-500.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-500.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-500.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-500.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-600 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-600.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-600.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-600.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-600.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-600.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-600.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-700 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-700.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-700.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-700.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-700.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-700.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-700.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* manrope-800 - latin */
|
|
||||||
@font-face {
|
|
||||||
font-display: swap;
|
|
||||||
|
|
||||||
font-family: 'Manrope';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 800;
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-800.eot');
|
|
||||||
src: url('../assets/fonts/manrope/manrope-v15-latin-800.eot?#iefix') format('embedded-opentype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-800.woff2') format('woff2'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-800.woff') format('woff'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-800.ttf') format('truetype'),
|
|
||||||
url('../assets/fonts/manrope/manrope-v15-latin-800.svg#Manrope') format('svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: 'Manrope';
|
|
||||||
}
|
|
||||||
|
|
||||||
html{
|
html{
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
scroll-snap-stop: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
body{
|
||||||
|
scroll-snap-stop: always;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (width <=48rem) {
|
@media (width <=48rem) {
|
||||||
@@ -119,6 +19,14 @@ html{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.font-en *{
|
||||||
|
font-family: 'Manrope';
|
||||||
|
}
|
||||||
|
|
||||||
|
body.font-fa *{
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary: #FA003F;
|
--primary: #FA003F;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useParams } from 'next/navigation';
|
|||||||
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
||||||
|
|
||||||
import { getWhoAreWeLinks } from './whoAreWe.const';
|
import { getWhoAreWeLinks } from './whoAreWe.const';
|
||||||
import { TLocales } from '@/locales/translates';
|
import { TLocales } from '@/models/layout';
|
||||||
|
|
||||||
export default function FooterMenu() {
|
export default function FooterMenu() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import routeFactory from '../routeFactory';
|
import routeFactory from '../routeFactory';
|
||||||
|
|
||||||
export function getQuickLinks(t: (key: string) => string, locale: string) {
|
export function getQuickLinks(t: any, locale: string) {
|
||||||
const routes = routeFactory(t, locale);
|
const routes = routeFactory(t, locale);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: routes.home.title,
|
title: routes.home.title,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import routeFactory from '../routeFactory';
|
import routeFactory from '../routeFactory';
|
||||||
|
|
||||||
export function getWhoAreWeLinks(t: (key: string) => string, locale: string) {
|
export function getWhoAreWeLinks(t: any, locale: string) {
|
||||||
const routes = routeFactory(t, locale);
|
const routes = routeFactory(t, locale);
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
type RouteFactory = (
|
type RouteFactory = (
|
||||||
t: (key: string) => string,
|
t: any,
|
||||||
locale: string,
|
locale: string,
|
||||||
) => {
|
) => {
|
||||||
home: { title: string; route: () => string };
|
home: { title: string; route: () => string };
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
/* manrope-200 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 200;
|
||||||
|
src: url('./manrope-v15-latin-200.eot');
|
||||||
|
src: url('./manrope-v15-latin-200.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-200.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-200.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-200.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-200.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-300 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300;
|
||||||
|
src: url('./manrope-v15-latin-300.eot');
|
||||||
|
src: url('./manrope-v15-latin-300.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-300.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-300.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-300.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-300.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('./manrope-v15-latin-regular.eot');
|
||||||
|
src: url('./manrope-v15-latin-regular.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-regular.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-regular.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-regular.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-regular.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-500 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
src: url('./manrope-v15-latin-500.eot');
|
||||||
|
src: url('./manrope-v15-latin-500.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-500.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-500.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-500.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-500.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-600 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
src: url('./manrope-v15-latin-600.eot');
|
||||||
|
src: url('./manrope-v15-latin-600.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-600.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-600.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-600.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-600.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: url('./manrope-v15-latin-700.eot');
|
||||||
|
src: url('./manrope-v15-latin-700.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-700.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-700.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-700.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-700.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* manrope-800 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-display: swap;
|
||||||
|
|
||||||
|
font-family: 'Manrope';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
src: url('./manrope-v15-latin-800.eot');
|
||||||
|
src: url('./manrope-v15-latin-800.eot?#iefix') format('embedded-opentype'),
|
||||||
|
url('./manrope-v15-latin-800.woff2') format('woff2'),
|
||||||
|
url('./manrope-v15-latin-800.woff') format('woff'),
|
||||||
|
url('./manrope-v15-latin-800.ttf') format('truetype'),
|
||||||
|
url('./manrope-v15-latin-800.svg#Manrope') format('svg');
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Thin.woff2') format('woff2');
|
||||||
|
font-weight: 100;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-ExtraLight.woff2') format('woff2');
|
||||||
|
font-weight: 200;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Light.woff2') format('woff2');
|
||||||
|
font-weight: 300;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Regular.woff2') format('woff2');
|
||||||
|
font-weight: 400;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Medium.woff2') format('woff2');
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-SemiBold.woff2') format('woff2');
|
||||||
|
font-weight: 600;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Bold.woff2') format('woff2');
|
||||||
|
font-weight: 700;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-ExtraBold.woff2') format('woff2');
|
||||||
|
font-weight: 800;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Vazirmatn';
|
||||||
|
src: url('./Vazirmatn-FD-Black.woff2') format('woff2');
|
||||||
|
font-weight: 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
@@ -7,10 +7,12 @@ import aboutImg_1 from './about-img-1.png';
|
|||||||
import aboutImg_2 from './about-img-2.png';
|
import aboutImg_2 from './about-img-2.png';
|
||||||
import homeProductBack from './home_product_back.jpeg';
|
import homeProductBack from './home_product_back.jpeg';
|
||||||
import whatWeDoImage from './what-we-do-image.png';
|
import whatWeDoImage from './what-we-do-image.png';
|
||||||
import blogImg_1 from './blog_img_1.jpeg'
|
import blogImg_1 from './blog_img_1.jpeg';
|
||||||
import projectImg_1 from './project_img_1.jpeg'
|
import projectImg_1 from './project_img_1.jpeg';
|
||||||
import product1 from './product1.png';
|
import product1 from './product1.png';
|
||||||
import notFound from './404.png'
|
import notFound from './404.png';
|
||||||
|
import langEn from './lang_en.png';
|
||||||
|
import langFa from './lang_fa.png';
|
||||||
export default {
|
export default {
|
||||||
homeBanner,
|
homeBanner,
|
||||||
logo,
|
logo,
|
||||||
@@ -25,4 +27,6 @@ export default {
|
|||||||
homeProductBack,
|
homeProductBack,
|
||||||
whatWeDoImage,
|
whatWeDoImage,
|
||||||
product1,
|
product1,
|
||||||
|
langEn,
|
||||||
|
langFa,
|
||||||
};
|
};
|
||||||
|
|||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1020 B |
@@ -10,12 +10,12 @@ import { useTranslations } from 'next-intl';
|
|||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
||||||
import { getWhoAreWeLinks } from '@/assets/constants/footerMenu/whoAreWe.const';
|
import { getWhoAreWeLinks } from '@/assets/constants/footerMenu/whoAreWe.const';
|
||||||
import { TLocales } from '@/locales/translates';
|
import { TLocales } from '@/models/layout';
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const locale = params.locale as TLocales;
|
const locale = (params.locale as TLocales) || 'en';
|
||||||
|
|
||||||
const quickLinks = getQuickLinks(t, locale);
|
const quickLinks = getQuickLinks(t, locale);
|
||||||
const whoAreWeLinks = getWhoAreWeLinks(t, locale);
|
const whoAreWeLinks = getWhoAreWeLinks(t, locale);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Icon, IconName } from '@/components/uikit/icons';
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import LanguageSwitch from '../languageSwitch';
|
||||||
|
|
||||||
export default function FooterInfo({
|
export default function FooterInfo({
|
||||||
withLogo,
|
withLogo,
|
||||||
@@ -42,6 +43,7 @@ export default function FooterInfo({
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<LanguageSwitch />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import images from '@/assets/images/images';
|
||||||
|
import { TLocales } from '@/models/layout';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export default function LanguageSwitch() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
const languages = [
|
||||||
|
{
|
||||||
|
title: 'EN',
|
||||||
|
image: images.langEn,
|
||||||
|
locale: 'en',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'FA',
|
||||||
|
image: images.langFa,
|
||||||
|
locale: 'fa',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const changeLocale = (locale: TLocales) => {
|
||||||
|
const currentLocale = pathname.split('/')[1];
|
||||||
|
if (currentLocale === locale) return;
|
||||||
|
let newPath = pathname.replace(/^\/(en|fa)/, '');
|
||||||
|
if (newPath === '/') newPath = '';
|
||||||
|
|
||||||
|
window.location.pathname = `/${locale}${newPath}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center gap-3">
|
||||||
|
{languages.map((language) => (
|
||||||
|
<div
|
||||||
|
className="flex cursor-pointer flex-col items-center justify-center gap-2 px-1"
|
||||||
|
onClick={() => changeLocale(language.locale as TLocales)}
|
||||||
|
key={language.title}
|
||||||
|
>
|
||||||
|
<Image src={language.image} alt={language.title} />
|
||||||
|
<span className="text-xs font-normal text-white">
|
||||||
|
{language.title}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
import 'leaflet/dist/leaflet.css';
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
delete (L.Icon.Default.prototype as any)._getIconUrl;
|
||||||
L.Icon.Default.mergeOptions({
|
L.Icon.Default.mergeOptions({
|
||||||
@@ -30,7 +31,7 @@ const ContactMap = () => {
|
|||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||||
/>
|
/>
|
||||||
<Marker position={position}>
|
<Marker position={position}>
|
||||||
<Popup>lastminute.com London Eye</Popup>
|
<Popup>Pasargad Bricks</Popup>
|
||||||
</Marker>
|
</Marker>
|
||||||
</MapContainer>
|
</MapContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { useTranslations } from 'next-intl';
|
|||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState<boolean>(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const locale = params.locale as string;
|
const locale = params.locale as string;
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import Button, { IconButton } from '@/components/uikit/button';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface PaginationProps {
|
||||||
|
page: number;
|
||||||
|
totalPages: number;
|
||||||
|
onPageChange?: (page: number) => void;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Pagination({
|
||||||
|
page,
|
||||||
|
totalPages,
|
||||||
|
onPageChange = () => {},
|
||||||
|
className,
|
||||||
|
}: PaginationProps) {
|
||||||
|
if (totalPages <= 1) return null;
|
||||||
|
|
||||||
|
const pages = [];
|
||||||
|
for (let i = 1; i <= totalPages; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav
|
||||||
|
className={`mt-8 flex items-center justify-center gap-2 ${className || ''}`}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
disabled={page === 1}
|
||||||
|
onClick={() => onPageChange(page - 1)}
|
||||||
|
className="disabled:opacity-50"
|
||||||
|
>
|
||||||
|
<
|
||||||
|
</IconButton>
|
||||||
|
{pages.map((p) => (
|
||||||
|
<IconButton
|
||||||
|
key={p}
|
||||||
|
className={`${p === page ? 'opacity-50' : ''}`}
|
||||||
|
onClick={() => onPageChange(p)}
|
||||||
|
>
|
||||||
|
{p}
|
||||||
|
</IconButton>
|
||||||
|
))}
|
||||||
|
<IconButton
|
||||||
|
disabled={page === totalPages}
|
||||||
|
onClick={() => onPageChange(page + 1)}
|
||||||
|
className="disabled:opacity-50"
|
||||||
|
>
|
||||||
|
>
|
||||||
|
</IconButton>
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ import Link from 'next/link';
|
|||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { ILayoutLocaleParams } from '@/models/layout';
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
import { TLocales } from '@/locales/translates';
|
import { TLocales } from '@/models/layout';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
locale: TLocales;
|
locale: TLocales;
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import Pagination from '@/components/layout/pagination';
|
||||||
|
import { getProducts } from '@/services/products';
|
||||||
|
import { IProductListProps } from './productList';
|
||||||
|
import ProductCard from '../productCard';
|
||||||
|
|
||||||
|
export default function ProductList({
|
||||||
|
initialProducts,
|
||||||
|
initialPagination,
|
||||||
|
categoryId,
|
||||||
|
}: IProductListProps) {
|
||||||
|
const [products, setProducts] = useState(initialProducts);
|
||||||
|
const [pagination, setPagination] = useState(initialPagination);
|
||||||
|
|
||||||
|
const fetchPage = (page: number) => {
|
||||||
|
getProducts(categoryId, page)
|
||||||
|
.then((res) => {
|
||||||
|
setProducts(res.data);
|
||||||
|
setPagination(res.pagination);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Failed to fetch products:', error);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||||
|
{products.map((product, index) => (
|
||||||
|
<ProductCard {...product} key={index} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<Pagination {...pagination} onPageChange={fetchPage} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { IPagination } from '@/models/response';
|
||||||
|
import { IProductCardProps } from '../productCard/productCard.d';
|
||||||
|
|
||||||
|
export interface IProductListProps {
|
||||||
|
initialProducts: IProductCardProps[];
|
||||||
|
initialPagination: IPagination;
|
||||||
|
categoryId: string;
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ export interface IBaseButtonProps
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
link?: URL | string;
|
link?: URL | string;
|
||||||
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITextButtonProps extends IBaseButtonProps {
|
export interface ITextButtonProps extends IBaseButtonProps {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const Button: React.FC<ITextButtonProps> = ({
|
|||||||
endIcon,
|
endIcon,
|
||||||
className,
|
className,
|
||||||
link,
|
link,
|
||||||
|
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 bg-primary relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''}`;
|
||||||
@@ -22,7 +23,12 @@ const Button: React.FC<ITextButtonProps> = ({
|
|||||||
</ButtonBody>
|
</ButtonBody>
|
||||||
</Link>
|
</Link>
|
||||||
) : (
|
) : (
|
||||||
<button disabled={disabled || loading} {...props} className={wrapperClass}>
|
<button
|
||||||
|
disabled={disabled || loading}
|
||||||
|
{...props}
|
||||||
|
onClick={onClick}
|
||||||
|
className={wrapperClass}
|
||||||
|
>
|
||||||
<ButtonBody {...props} loading={loading} endIcon={endIcon}>
|
<ButtonBody {...props} loading={loading} endIcon={endIcon}>
|
||||||
{children}
|
{children}
|
||||||
</ButtonBody>
|
</ButtonBody>
|
||||||
@@ -82,6 +88,7 @@ export const IconButton: React.FC<IIconButtonProps> = ({
|
|||||||
size = 'medium',
|
size = 'medium',
|
||||||
color = 'primary',
|
color = 'primary',
|
||||||
className,
|
className,
|
||||||
|
onClick,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const colorClass =
|
const colorClass =
|
||||||
@@ -95,6 +102,7 @@ export const IconButton: React.FC<IIconButtonProps> = ({
|
|||||||
className={`group relative flex cursor-pointer items-center justify-center rounded-[0.5625rem] ${colorClass} ${sizeClass} ${className || ''} `}
|
className={`group relative flex cursor-pointer items-center justify-center rounded-[0.5625rem] ${colorClass} ${sizeClass} ${className || ''} `}
|
||||||
disabled={disabled || loading}
|
disabled={disabled || loading}
|
||||||
{...props}
|
{...props}
|
||||||
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@@ -1,30 +1,18 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
// Create an Axios instance
|
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api',
|
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3000/api',
|
||||||
withCredentials: true, // Send cookies with requests if needed
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
withCredentials: true, // Enable sending cookies with requests
|
||||||
});
|
});
|
||||||
|
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
// Example: Add lang cookie from browser (client-side only)
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
const match = document.cookie.match(/(^| )lang=([^;]+)/);
|
|
||||||
if (match) {
|
|
||||||
config.headers['lang'] = decodeURIComponent(match[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
);
|
);
|
||||||
|
|
||||||
api.interceptors.response.use(
|
|
||||||
(response) => response,
|
|
||||||
(error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export default api;
|
export default api;
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
const en = {
|
|
||||||
pages_home_title: 'Home',
|
|
||||||
pages_home_description: 'Welcome to the home page',
|
|
||||||
pages_about_title: 'About Us',
|
|
||||||
pages_about_description: 'Learn more about us on this page',
|
|
||||||
pages_notFound_title: '404 Not Found Page',
|
|
||||||
pages_notFound_description: 'You can either head back to the Homepage or try a search.',
|
|
||||||
pages_notFound_button:"Go To Home",
|
|
||||||
pages_contact_title: 'Contact Us',
|
|
||||||
pages_product_categories_title: 'Products',
|
|
||||||
pages_products_title: 'Products',
|
|
||||||
pages_blog_title: 'Blog',
|
|
||||||
pages_projects_title: 'Projects',
|
|
||||||
pages_contact_description: 'Get in touch with us through this page',
|
|
||||||
com_home_banner_title: 'Excellence innovating',
|
|
||||||
com_home_banner_title_bold: 'industry for today',
|
|
||||||
com_home_banner_description:
|
|
||||||
'At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes. ',
|
|
||||||
com_home_banner_button: ' Explore More',
|
|
||||||
com_home_Banner_link1: ' Advanced Manufacturing Solutions',
|
|
||||||
com_home_Banner_link2: 'Quality Assurance Systems',
|
|
||||||
com_home_Banner_link3: 'State-of-the-Art Technology',
|
|
||||||
|
|
||||||
com_about_info_title: 'Building quality through',
|
|
||||||
com_about_info_title_bold: 'industrial innovation',
|
|
||||||
com_about_info_description:
|
|
||||||
'At the heart of our operations is a commitment to delivering superior products through cutting-edge technology and innovative processes.',
|
|
||||||
com_about_info_years_experience: 'years of experience',
|
|
||||||
com_about_info_achievements_1: 'Sustainable Manufacturing',
|
|
||||||
com_about_info_achievements_2: 'Advanced Automation',
|
|
||||||
com_about_info_achievements_3: 'Efficient Production Processes',
|
|
||||||
com_about_info_achievements_4: 'Reliable Delivery Services',
|
|
||||||
|
|
||||||
com_home_products_title: 'The Qualified bricks for Your Projects',
|
|
||||||
com_home_products_description:
|
|
||||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. ',
|
|
||||||
|
|
||||||
com_contact_touch_title: 'Get in touch',
|
|
||||||
com_contact_touch_title_bold: 'with us',
|
|
||||||
com_contact_touch_description: 'Reach out for any inquiries, support, or to discuss how we can meet your industrial needs.',
|
|
||||||
com_contact_touch_option_1:'Contact',
|
|
||||||
com_contact_touch_option_2:'E-mail',
|
|
||||||
com_contact_touch_option_3:'Our Address',
|
|
||||||
com_contact_touch_option_3_description:'37 San Juan Lane Graaf Florisstraat 22A,3021 CH',
|
|
||||||
com_contact_title: 'Contact ',
|
|
||||||
com_contact_title_bold: 'me',
|
|
||||||
com_contact_first_name: 'Enter first name',
|
|
||||||
com_contact_last_name: 'Enter last name',
|
|
||||||
com_contact_email: 'Enter your e-mail',
|
|
||||||
com_contact_phone: 'Enter your phone no.',
|
|
||||||
com_contact_textarea: 'Write Message',
|
|
||||||
footer_title: 'Industrial Solutions for a Modern World',
|
|
||||||
footer_description:
|
|
||||||
'We are committed to providing personalized industrial solutions that meet the unique needs of our clients. Our team of experts is dedicated to delivering high-quality products and services that drive success in today’s competitive market.',
|
|
||||||
footer_slogan:
|
|
||||||
'We are committed to providing personalized industrial solutions.',
|
|
||||||
footer_links_quick_links: 'Quick Links',
|
|
||||||
footer_links_who_we_are: 'Who We Are',
|
|
||||||
footer_links_contact: 'Contact',
|
|
||||||
footer_copyright: 'Copyright © 2025 All Rights Reserved.',
|
|
||||||
footer_links_address: '123 Industrial Ave, Suite 456, City, Country',
|
|
||||||
|
|
||||||
// Home Page Components
|
|
||||||
com_home_story_title: 'Our Story',
|
|
||||||
com_home_story_slogan: 'Building quality through',
|
|
||||||
com_home_story_slogan_bold: 'industrial innovation',
|
|
||||||
com_home_story_description:
|
|
||||||
'We are a leading provider of industrial solutions, dedicated to delivering high-quality products and services that meet the unique needs of our clients. Our team of experts is committed to driving success in today’s competitive market through innovation and excellence.',
|
|
||||||
com_home_story_projects_count: 'Completed Projects',
|
|
||||||
com_home_story_customer_count: 'Happy Customers',
|
|
||||||
com_home_story_experience: 'Years of Experience',
|
|
||||||
|
|
||||||
// Products Page Components
|
|
||||||
com_products_category_Ghazaghi_title: 'Ghazaghi',
|
|
||||||
com_products_category_Ghermez_title: 'Ghermez',
|
|
||||||
com_products_category_Semirom_title: 'Semirom',
|
|
||||||
com_products_category_Zard_title: 'Zard',
|
|
||||||
com_products_category_types_count: ({ count }: { count: number }) =>
|
|
||||||
`${count} Types`,
|
|
||||||
com_products_category_show_all_CTA: 'Show Products',
|
|
||||||
pages_products_content_title:
|
|
||||||
'Brick Classification: Understanding the Variety and Applications',
|
|
||||||
pages_products_content_description: `Bricks, as one of humanity's oldest and most widely used building materials, have evolved significantly throughout history. Today, they are produced and supplied in an incredibly diverse range, allowing engineers and architects to choose the most suitable brick for any project and need, from traditional and historic structures to modern and advanced buildings. To better understand this diversity, we can classify bricks based on several key criteria, each with its own specific characteristics and applications.
|
|
||||||
\n 1. Classification by Manufacturing Method: From Tradition to Advanced Industry
|
|
||||||
The method by which bricks are produced directly impacts their appearance, texture, and properties.
|
|
||||||
Hand-moulded/Stock Bricks: These bricks are produced by manually pressing clay into molds. The result is a brick with a relatively rough texture, uneven edges, and a more traditional appearance, where each piece feels unique. This type of brick is often used for restoration projects or for constructing buildings with a rustic or antique style.
|
|
||||||
Machine-made Bricks: These bricks are produced using industrial machinery, resulting in much higher precision in dimensions and uniformity in appearance. Machine-made bricks themselves are divided into two main categories:
|
|
||||||
Perforated/Hollow Bricks: These bricks have holes, offering several advantages: they reduce building weight, improve thermal and acoustic insulation, and enhance mortar adhesion.
|
|
||||||
Solid Bricks: These bricks are without holes and possess very high compressive strength. They are ideal for load-bearing structures and walls requiring maximum strength.
|
|
||||||
2. Classification by Raw Material Type and Composition: From Clay to Concrete
|
|
||||||
The material from which a brick is made determines its physical and chemical properties.`,
|
|
||||||
|
|
||||||
com_products_products_category_title: ({ title }: { title: string }) =>
|
|
||||||
`Products of ${title} Category`,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default en;
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { Translates } from './translates';
|
|
||||||
|
|
||||||
const fa = {
|
|
||||||
pages_home_title: 'صفحهی اصلی',
|
|
||||||
pages_home_description: 'Welcome to the home page',
|
|
||||||
pages_about_title: 'About Us',
|
|
||||||
pages_about_description: 'Learn more about us on this page',
|
|
||||||
pages_notFound_title: '404 Not Found Page',
|
|
||||||
pages_notFound_description: 'You can either head back to the Homepage or try a search.',
|
|
||||||
pages_notFound_button:"Go To Home",
|
|
||||||
pages_contact_title: 'Contact Us',
|
|
||||||
pages_product_categories_title: 'Product',
|
|
||||||
pages_blog_title: 'Blog',
|
|
||||||
pages_projects_title: 'Project',
|
|
||||||
pages_contact_description: 'Get in touch with us through this page',
|
|
||||||
com_home_banner_title: 'Excellence innovating',
|
|
||||||
com_home_banner_title_bold: 'industry for today',
|
|
||||||
com_home_banner_description:
|
|
||||||
'At the heart of our operations is a commitment to delivering superior products through cutting- <br/> edge technology and innovative processes. ',
|
|
||||||
com_home_banner_button: 'اطلاعات بیشتر',
|
|
||||||
com_home_Banner_link1: ' Advanced Manufacturing Solutions',
|
|
||||||
com_home_Banner_link2: 'Quality Assurance Systems',
|
|
||||||
com_home_Banner_link3: 'State-of-the-Art Technology',
|
|
||||||
com_contact_touch_title: 'Get in touch',
|
|
||||||
com_contact_touch_title_bold: 'with us',
|
|
||||||
com_contact_touch_description: 'Reach out for any inquiries, support, or to discuss how we can meet your industrial needs.',
|
|
||||||
com_contact_touch_option_1:'Contact',
|
|
||||||
com_contact_touch_option_2:'E-mail',
|
|
||||||
com_contact_touch_option_3:'Our Address',
|
|
||||||
com_contact_touch_option_3_description:'37 San Juan Lane Graaf Florisstraat 22A,3021 CH',
|
|
||||||
com_contact_title: 'Contact ',
|
|
||||||
com_contact_title_bold: 'me',
|
|
||||||
com_contact_first_name: 'Enter first name',
|
|
||||||
com_contact_last_name: 'Enter last name',
|
|
||||||
com_contact_email: 'Enter your e-mail',
|
|
||||||
com_contact_phone: 'Enter your phone no.',
|
|
||||||
com_contact_textarea: 'Write Message',
|
|
||||||
} as Translates;
|
|
||||||
|
|
||||||
export default fa;
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import en from './en';
|
|
||||||
import fa from './fa';
|
|
||||||
|
|
||||||
// Universal getCookie function
|
|
||||||
export function getCookie(name: string): string | undefined {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
// Client-side
|
|
||||||
const match = document.cookie.match(
|
|
||||||
new RegExp('(^| )' + name + '=([^;]+)'),
|
|
||||||
);
|
|
||||||
return match ? decodeURIComponent(match[2]) : undefined;
|
|
||||||
} else {
|
|
||||||
// Server-side (Next.js App Router)
|
|
||||||
try {
|
|
||||||
// Dynamically import to avoid errors in client bundle
|
|
||||||
const { cookies } = require('next/headers');
|
|
||||||
return cookies().get(name)?.value;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Universal setCookie function to change language
|
|
||||||
export function changeLanguage(lang: TLocales, days = 365) {
|
|
||||||
if (typeof window !== 'undefined') {
|
|
||||||
// Client-side
|
|
||||||
const expires = new Date(Date.now() + days * 864e5).toUTCString();
|
|
||||||
document.cookie = `lang=${encodeURIComponent(lang)}; expires=${expires}; path=/`;
|
|
||||||
} else {
|
|
||||||
// Server-side (Next.js App Router)
|
|
||||||
try {
|
|
||||||
const { cookies } = require('next/headers');
|
|
||||||
cookies().set('lang', lang, { path: '/', maxAge: days * 24 * 60 * 60 });
|
|
||||||
} catch {
|
|
||||||
// No-op on server if not available
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const locales = {
|
|
||||||
en: en as unknown as Translates,
|
|
||||||
fa,
|
|
||||||
} as Record<TLocales, Translates>;
|
|
||||||
|
|
||||||
export const translates = (
|
|
||||||
key: keyof Translates,
|
|
||||||
dynamicValue?: Record<string, string | number>,
|
|
||||||
): string | ((dynamicValue: Record<string, string | number>) => string) => {
|
|
||||||
const lang = (getCookie('lang') || 'en') as TLocales;
|
|
||||||
const value = locales[lang][key] || locales.en[key] || key;
|
|
||||||
if (typeof value === 'function') {
|
|
||||||
return value(dynamicValue || {});
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const t = (
|
|
||||||
key: keyof Translates,
|
|
||||||
dynamicValue?: Record<string, string | number>,
|
|
||||||
) => '';
|
|
||||||
|
|
||||||
export type TLocales = 'en' | 'fa';
|
|
||||||
|
|
||||||
export type Translates = {
|
|
||||||
[K in keyof typeof en]:
|
|
||||||
| string
|
|
||||||
| ((dynamicValue: Record<string, string | number>) => string);
|
|
||||||
};
|
|
||||||
@@ -1,8 +1,43 @@
|
|||||||
import createMiddleware from 'next-intl/middleware';
|
import { NextResponse } from 'next/server';
|
||||||
import { routing } from './i18n/routing';
|
import type { NextRequest } from 'next/server';
|
||||||
|
|
||||||
export default createMiddleware(routing);
|
const PUBLIC_FILE = /\.(.*)$/;
|
||||||
|
const locales = ['en', 'fa'];
|
||||||
|
const defaultLocale = 'en';
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl;
|
||||||
|
|
||||||
|
// Ignore public files and API routes
|
||||||
|
if (pathname.startsWith('/api') || PUBLIC_FILE.test(pathname)) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the pathname already includes a locale
|
||||||
|
const matchedLocale = locales.find(
|
||||||
|
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const locale = matchedLocale || defaultLocale;
|
||||||
|
|
||||||
|
let response: NextResponse;
|
||||||
|
|
||||||
|
// If locale is missing, redirect and set cookie
|
||||||
|
const pathnameIsMissingLocale = !matchedLocale;
|
||||||
|
if (pathnameIsMissingLocale) {
|
||||||
|
response = NextResponse.redirect(
|
||||||
|
new URL(`/${defaultLocale}${pathname}`, request.url),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
response = NextResponse.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the locale cookie for all responses
|
||||||
|
response.cookies.set('locale', locale, { path: '/' });
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
|
matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
export interface IPageParams<T = {}> extends Promise<ILayoutLocaleParams & T> {}
|
||||||
export interface ILayoutLocaleParams {
|
export interface ILayoutLocaleParams {
|
||||||
locale: TLocales;
|
locale: TLocales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TLocales = 'en' | 'fa';
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export interface IResponse<T> {
|
||||||
|
pagination: IPagination;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPagination {
|
||||||
|
page: number;
|
||||||
|
perPage: number;
|
||||||
|
total: number;
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
@@ -1,32 +1,55 @@
|
|||||||
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
|
'use server';
|
||||||
import type { IProductCardProps } from '@/components/pages/products/productCard.d';
|
|
||||||
import api from '@/lib/axios';
|
|
||||||
|
|
||||||
export function getProductCategories() {
|
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
|
||||||
|
import type { IProductCardProps } from '@/components/pages/products/productCard/productCard.d';
|
||||||
|
import api from '@/lib/axios';
|
||||||
|
import { IResponse } from '@/models/response';
|
||||||
|
import { cookies } from 'next/headers';
|
||||||
|
|
||||||
|
export async function getProductCategories() {
|
||||||
|
const cookiesStore = await cookies();
|
||||||
return api
|
return api
|
||||||
.get('/product_categories')
|
.get('/product_categories', {
|
||||||
|
headers: {
|
||||||
|
Cookie: cookiesStore.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
return Promise.resolve(data.data);
|
return Promise.resolve(data.data);
|
||||||
})
|
})
|
||||||
.catch((error) => Promise.reject(error));
|
.catch((error) => Promise.reject(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProductCategoryById(
|
export async function getProductCategoryById(
|
||||||
categoryId: string,
|
categoryId: string,
|
||||||
): Promise<IProductCategoryCardProps> {
|
): Promise<IProductCategoryCardProps> {
|
||||||
|
const cookiesStore = await cookies();
|
||||||
return api
|
return api
|
||||||
.get(`/product_categories/${categoryId}`)
|
.get(`/product_categories/${categoryId}`, {
|
||||||
|
headers: {
|
||||||
|
Cookie: cookiesStore.toString(),
|
||||||
|
},
|
||||||
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
return Promise.resolve(data.data);
|
return Promise.resolve(data.data);
|
||||||
})
|
})
|
||||||
.catch((error) => Promise.reject(error));
|
.catch((error) => Promise.reject(error));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProducts(categoryId: string): Promise<IProductCardProps[]> {
|
export async function getProducts(
|
||||||
|
categoryId: string,
|
||||||
|
page = 1,
|
||||||
|
): Promise<IResponse<IProductCardProps[]>> {
|
||||||
|
const cookiesStore = await cookies();
|
||||||
return api
|
return api
|
||||||
.get(`/product_categories/${categoryId}/products`)
|
.get(`/product_categories/${categoryId}/products`, {
|
||||||
|
headers: {
|
||||||
|
Cookie: cookiesStore.toString(),
|
||||||
|
},
|
||||||
|
params: { page },
|
||||||
|
})
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
return Promise.resolve(data.data);
|
return Promise.resolve(data);
|
||||||
})
|
})
|
||||||
.catch((error) => Promise.reject(error));
|
.catch((error) => Promise.reject(error));
|
||||||
}
|
}
|
||||||
|
|||||||