feat: implement internationalization and routing for locale-based pages
- Added RootLayout component to handle locale and message loading. - Created Home page with header, about info, products, and story sections. - Developed ProductPage for displaying product categories and products. - Implemented ProductsPage to list product categories with banners. - Added Projects page with a grid layout for project display. - Configured server settings for port and host. - Established navigation and routing for internationalization. - Set up request handling for locale and message retrieval. - Defined routing structure for supported locales. - Created middleware for locale-based routing. - Defined layout parameters for locale handling.
This commit is contained in:
Vendored
+4
@@ -0,0 +1,4 @@
|
|||||||
|
declare module '*.json' {
|
||||||
|
const value: any;
|
||||||
|
export default value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { routing } from '@/i18n/routing';
|
||||||
|
import messages from './messages/en.json';
|
||||||
|
|
||||||
|
declare module 'next-intl' {
|
||||||
|
interface AppConfig {
|
||||||
|
Locale: (typeof routing.locales)[number];
|
||||||
|
Messages: typeof messages;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// This file is auto-generated by next-intl, do not edit directly.
|
||||||
|
// See: https://next-intl.dev/docs/workflows/typescript#messages-arguments
|
||||||
|
|
||||||
|
declare const messages: {
|
||||||
|
"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_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.",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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 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 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.",
|
||||||
|
|
||||||
|
"com_products_products_category_title": "Products of {title} Category"
|
||||||
|
};
|
||||||
|
export default messages;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"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_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.",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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 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 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.",
|
||||||
|
|
||||||
|
"com_products_products_category_title": "Products of {title} Category"
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"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_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.",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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",
|
||||||
|
|
||||||
|
"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 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 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.",
|
||||||
|
|
||||||
|
"com_products_products_category_title": "Products of {title} Category"
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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,4 @@
|
|||||||
|
export default {
|
||||||
|
locales: ['en', 'fa'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
};
|
||||||
+10
-3
@@ -1,5 +1,12 @@
|
|||||||
import type { NextConfig } from 'next';
|
import { NextConfig } from 'next';
|
||||||
|
import createNextIntlPlugin from 'next-intl/plugin';
|
||||||
|
|
||||||
const nextConfig: NextConfig = {};
|
const withNextIntl = createNextIntlPlugin({
|
||||||
|
experimental: {
|
||||||
|
createMessagesDeclaration: './messages/en.json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default nextConfig;
|
const config: NextConfig = {};
|
||||||
|
|
||||||
|
export default withNextIntl(config);
|
||||||
|
|||||||
Generated
+124
-1
@@ -11,6 +11,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
|
"next-intl": "^4.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-leaflet": "^5.0.0"
|
"react-leaflet": "^5.0.0"
|
||||||
@@ -218,6 +219,60 @@
|
|||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract": {
|
||||||
|
"version": "2.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.4.tgz",
|
||||||
|
"integrity": "sha512-qrycXDeaORzIqNhBOx0btnhpD1c+/qFIHAN9znofuMJX6QBwtbrmlpWfD4oiUUD2vJUOIYFA/gYtg2KAMGG7sA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/intl-localematcher": "0.6.1",
|
||||||
|
"decimal.js": "^10.4.3",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/fast-memoize": {
|
||||||
|
"version": "2.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz",
|
||||||
|
"integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||||
|
"version": "2.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.2.tgz",
|
||||||
|
"integrity": "sha512-AfiMi5NOSo2TQImsYAg8UYddsNJ/vUEv/HaNqiFjnI3ZFfWihUtD5QtuX6kHl8+H+d3qvnE/3HZrfzgdWpsLNA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"@formatjs/icu-skeleton-parser": "1.8.14",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||||
|
"version": "1.8.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.14.tgz",
|
||||||
|
"integrity": "sha512-i4q4V4qslThK4Ig8SxyD76cp3+QJ3sAqr7f6q9VVfeGtxG9OhiAk3y9XF6Q41OymsKzsGQ6OQQoJNY4/lI8TcQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@formatjs/intl-localematcher": {
|
||||||
|
"version": "0.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz",
|
||||||
|
"integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.1",
|
"version": "0.19.1",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||||
@@ -939,6 +994,11 @@
|
|||||||
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
|
"integrity": "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@schummar/icu-type-parser": {
|
||||||
|
"version": "1.21.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz",
|
||||||
|
"integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw=="
|
||||||
|
},
|
||||||
"node_modules/@swc/counter": {
|
"node_modules/@swc/counter": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
|
||||||
@@ -2336,6 +2396,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/decimal.js": {
|
||||||
|
"version": "10.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz",
|
||||||
|
"integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw=="
|
||||||
|
},
|
||||||
"node_modules/deep-is": {
|
"node_modules/deep-is": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
||||||
@@ -3527,6 +3592,17 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/intl-messageformat": {
|
||||||
|
"version": "10.7.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.16.tgz",
|
||||||
|
"integrity": "sha512-UmdmHUmp5CIKKjSoE10la5yfU+AYJAaiYLsodbjL4lji83JNvgOQUjGaGhGrpFCb0Uh7sl7qfP1IyILa8Z40ug==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/ecma402-abstract": "2.3.4",
|
||||||
|
"@formatjs/fast-memoize": "2.2.7",
|
||||||
|
"@formatjs/icu-messageformat-parser": "2.11.2",
|
||||||
|
"tslib": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-array-buffer": {
|
"node_modules/is-array-buffer": {
|
||||||
"version": "3.0.5",
|
"version": "3.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||||
@@ -4475,6 +4551,14 @@
|
|||||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/negotiator": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.3.3",
|
"version": "15.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.3.3.tgz",
|
||||||
@@ -4528,6 +4612,32 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/next-intl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-JNJRjc7sdnfUxhZmGcvzDszZ60tQKrygV/VLsgzXhnJDxQPn1cN2rVpc53adA1SvBJwPK2O6Sc6b4gYSILjCzw==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/amannn"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/intl-localematcher": "^0.5.4",
|
||||||
|
"negotiator": "^1.0.0",
|
||||||
|
"use-intl": "^4.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": 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",
|
||||||
@@ -5822,7 +5932,7 @@
|
|||||||
"version": "5.8.3",
|
"version": "5.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz",
|
||||||
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
@@ -5896,6 +6006,19 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-intl": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-mQvDYFvoGn+bm/PWvlQOtluKCknsQ5a9F1Cj0hMfBjMBVTwnOqLPd6srhjvVdEQEQFVyHM1PfyifKqKYb11M9Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@formatjs/fast-memoize": "^2.2.0",
|
||||||
|
"@schummar/icu-type-parser": "1.21.5",
|
||||||
|
"intl-messageformat": "^10.5.14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"axios": "^1.9.0",
|
"axios": "^1.9.0",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"next": "15.3.3",
|
"next": "15.3.3",
|
||||||
|
"next-intl": "^4.1.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-leaflet": "^5.0.0"
|
"react-leaflet": "^5.0.0"
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import images from '@/assets/images/images';
|
||||||
|
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
||||||
|
import AboutTopInfo from '@/components/pages/about/TopInfo';
|
||||||
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { setRequestLocale } from 'next-intl/server';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
|
export default async function AboutUs({
|
||||||
|
params,
|
||||||
|
}: Readonly<{
|
||||||
|
params: ILayoutLocaleParams;
|
||||||
|
}>) {
|
||||||
|
const { locale } = await params;
|
||||||
|
|
||||||
|
// Enable static rendering
|
||||||
|
setRequestLocale(locale);
|
||||||
|
|
||||||
|
const t = await getTranslations({ locale });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="flex flex-col items-center justify-between">
|
||||||
|
<InnerPageBanner
|
||||||
|
title={t('pages_about_title')}
|
||||||
|
imageSrc={images.aboutBanner.src}
|
||||||
|
/>
|
||||||
|
<AboutTopInfo />
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
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 { t } from '@/locales/translates';
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
async function getData() {
|
export default async function Blog({
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
params,
|
||||||
return { message: 'This data was fetched on the server!' };
|
}: {
|
||||||
}
|
params: ILayoutLocaleParams;
|
||||||
|
}) {
|
||||||
export default async function Blog() {
|
const t = await getTranslations({ locale: params.locale });
|
||||||
const data = await getData();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
<InnerPageBanner
|
<InnerPageBanner
|
||||||
title={t('pages_blog_title') as string}
|
title={t('pages_blog_title')}
|
||||||
imageSrc={images.blogBanner.src}
|
imageSrc={images.blogBanner.src}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -3,21 +3,21 @@ import InnerPageBanner from '@/components/layout/innerPages/banner';
|
|||||||
import ContactMap from '@/components/layout/map';
|
import ContactMap from '@/components/layout/map';
|
||||||
import ContactForm from '@/components/pages/contact/ContactMeForm';
|
import ContactForm from '@/components/pages/contact/ContactMeForm';
|
||||||
import TouchUs from '@/components/pages/contact/TouchUs';
|
import TouchUs from '@/components/pages/contact/TouchUs';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
|
|
||||||
async function getData() {
|
export default async function Contact({
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
params,
|
||||||
return { message: 'This data was fetched on the server!' };
|
}: {
|
||||||
}
|
params: ILayoutLocaleParams;
|
||||||
|
}) {
|
||||||
export default async function Contact() {
|
const t = await getTranslations({ locale: params.locale });
|
||||||
const data = await getData();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
<InnerPageBanner
|
<InnerPageBanner
|
||||||
title={t('pages_contact_title') as string}
|
title={t('pages_contact_title')}
|
||||||
imageSrc={images.blogBanner.src}
|
imageSrc={images.blogBanner.src}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,43 @@
|
|||||||
|
import Footer from '@/components/layout/footer/Footer';
|
||||||
|
import '@/app/globals.css';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
import { notFound } from 'next/navigation';
|
||||||
|
import { NextIntlClientProvider } from 'next-intl';
|
||||||
|
import type { ILayoutLocaleParams } from '@/models/layout.d';
|
||||||
|
|
||||||
|
// export const metadata: Metadata = {
|
||||||
|
// title: 'Create Next App',
|
||||||
|
// description: 'Generated by create next app',
|
||||||
|
// };
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
params,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
params: ILayoutLocaleParams;
|
||||||
|
}>) {
|
||||||
|
const locale = params.locale;
|
||||||
|
console.log(locale);
|
||||||
|
|
||||||
|
let messages;
|
||||||
|
try {
|
||||||
|
console.log(await import(`../../../messages/en.json`));
|
||||||
|
messages = (await import(`../../../messages/en.json`)).default;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
|
||||||
|
<body className="min-h-svh w-full overflow-x-hidden">
|
||||||
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</NextIntlClientProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,20 +2,23 @@ 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 { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
async function getData() {
|
interface IPageParams {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
params: ILayoutLocaleParams;
|
||||||
return { message: 'This data was fetched on the server!' };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Home() {
|
export default async function Home({ params }: IPageParams) {
|
||||||
const data = await getData();
|
const { locale } = await params;
|
||||||
|
|
||||||
|
const t = await getTranslations({ locale });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
<Header />
|
<Header />
|
||||||
<AboutTopInfo />
|
<AboutTopInfo />
|
||||||
<HomePageProducts />
|
<HomePageProducts locale={locale} />
|
||||||
<HomeStorySection className="my-24" />
|
<HomeStorySection className="my-24" />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
+8
-4
@@ -5,9 +5,11 @@ 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 ProductCard from '@/components/pages/products/ProductCard';
|
||||||
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
import { getProductCategoryById, getProducts } from '@/services/products';
|
import { getProductCategoryById, getProducts } from '@/services/products';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
interface IParams {
|
interface IParams extends ILayoutLocaleParams {
|
||||||
product_category_id: string;
|
product_category_id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,15 +18,17 @@ export default async function ProductPage({
|
|||||||
}: {
|
}: {
|
||||||
params: Promise<IParams>;
|
params: Promise<IParams>;
|
||||||
}) {
|
}) {
|
||||||
const { product_category_id: productCategoryId } = await params;
|
const { product_category_id: productCategoryId, locale } = await params;
|
||||||
|
const t = await getTranslations({ locale });
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
|
||||||
const productCategory = await getProductCategoryById(productCategoryId);
|
const productCategory = await getProductCategoryById(productCategoryId);
|
||||||
const products = await getProducts(productCategoryId);
|
const products = await getProducts(productCategoryId);
|
||||||
|
|
||||||
const breadcrumbItems = [
|
const breadcrumbItems = [
|
||||||
{
|
{
|
||||||
...routeFactory.productCategories,
|
...routes.productCategories,
|
||||||
href: routeFactory.productCategories.route(),
|
href: routes.productCategories.route(),
|
||||||
},
|
},
|
||||||
{ title: productCategory.title },
|
{ title: productCategory.title },
|
||||||
] as IBreadcrumbItem[];
|
] as IBreadcrumbItem[];
|
||||||
@@ -2,17 +2,24 @@ 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 { t } from '@/locales/translates';
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
import { getProductCategories } from '@/services/products';
|
import { getProductCategories } from '@/services/products';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
export default async function ProductsPage() {
|
export default async function ProductsPage({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: ILayoutLocaleParams;
|
||||||
|
}) {
|
||||||
|
const awaitedParams = await params;
|
||||||
|
const t = await getTranslations({ locale: awaitedParams.locale });
|
||||||
const productCategories: IProductCategoryCardProps[] =
|
const productCategories: IProductCategoryCardProps[] =
|
||||||
await getProductCategories();
|
await getProductCategories();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<InnerPageBanner
|
<InnerPageBanner
|
||||||
title={t('pages_product_categories_title') as string}
|
title={t('pages_product_categories_title')}
|
||||||
imageSrc={images.aboutBanner.src}
|
imageSrc={images.aboutBanner.src}
|
||||||
/>
|
/>
|
||||||
<div className="container-fluid mx-auto my-24">
|
<div className="container-fluid mx-auto my-24">
|
||||||
@@ -23,10 +30,10 @@ export default async function ProductsPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-20">
|
<div className="mt-20">
|
||||||
<h2 className="mb-4 text-2xl font-semibold text-gray-500">
|
<h2 className="mb-4 text-2xl font-semibold text-gray-500">
|
||||||
{t('pages_products_content_title') as string}
|
{t('pages_products_content_title')}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-base font-light text-gray-400">
|
<p className="text-base font-light text-gray-400">
|
||||||
{t('pages_products_content_description') as string}
|
{t('pages_products_content_description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
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 { t } from '@/locales/translates';
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
async function getData() {
|
export default async function Projects({
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
params,
|
||||||
return { message: 'This data was fetched on the server!' };
|
}: {
|
||||||
}
|
params: ILayoutLocaleParams;
|
||||||
|
}) {
|
||||||
export default async function Projects() {
|
const t = await getTranslations({ locale: params.locale });
|
||||||
const data = await getData();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex flex-col items-center justify-between">
|
<main className="flex flex-col items-center justify-between">
|
||||||
<InnerPageBanner
|
<InnerPageBanner
|
||||||
title={t('pages_projects_title') as string}
|
title={t('pages_projects_title')}
|
||||||
imageSrc={images.blogBanner.src}
|
imageSrc={images.blogBanner.src}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import images from '@/assets/images/images';
|
|
||||||
import Header from '@/components/layout/header';
|
|
||||||
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
|
||||||
import SectionTitle from '@/components/layout/sectionTitle';
|
|
||||||
import AboutTopInfo from '@/components/pages/about/TopInfo';
|
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
|
|
||||||
async function getData() {
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
||||||
return { message: 'This data was fetched on the server!' };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function AboutUs() {
|
|
||||||
const data = await getData();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<main className="flex flex-col items-center justify-between">
|
|
||||||
<InnerPageBanner
|
|
||||||
title={t('pages_about_title') as string}
|
|
||||||
imageSrc={images.aboutBanner.src}
|
|
||||||
/>
|
|
||||||
<AboutTopInfo />
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import Footer from '@/components/layout/footer/Footer';
|
|
||||||
import './globals.css';
|
|
||||||
import { cookies } from 'next/headers';
|
|
||||||
import 'leaflet/dist/leaflet.css';
|
|
||||||
|
|
||||||
// export const metadata: Metadata = {
|
|
||||||
// title: 'Create Next App',
|
|
||||||
// description: 'Generated by create next app',
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
|
||||||
children,
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
}>) {
|
|
||||||
const cookieStore = await cookies();
|
|
||||||
const locale = cookieStore.get('lang')?.value || 'en';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang={locale} dir={locale === 'fa' ? 'rtl' : 'ltr'}>
|
|
||||||
<body className="min-h-svh w-full overflow-x-hidden">
|
|
||||||
{children}
|
|
||||||
<Footer />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,19 @@
|
|||||||
import { quickLinks } from './quickLinks.const';
|
'use client';
|
||||||
import { whoAreWeLinks } from './whoAreWe.const';
|
|
||||||
|
|
||||||
export const footerMenu = {
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
||||||
|
|
||||||
|
import { getWhoAreWeLinks } from './whoAreWe.const';
|
||||||
|
import { TLocales } from '@/locales/translates';
|
||||||
|
|
||||||
|
export default function FooterMenu() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const { locale } = useParams();
|
||||||
|
const quickLinks = getQuickLinks(t, locale as TLocales);
|
||||||
|
const whoAreWeLinks = getWhoAreWeLinks(t, locale as TLocales);
|
||||||
|
return {
|
||||||
quickLinks,
|
quickLinks,
|
||||||
whoAreWeLinks,
|
whoAreWeLinks,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
import routeFactory from '../routeFactory';
|
import routeFactory from '../routeFactory';
|
||||||
|
|
||||||
export const quickLinks = [
|
export function getQuickLinks(t: (key: string) => string, locale: string) {
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
title: routeFactory.home.title,
|
title: routes.home.title,
|
||||||
link: routeFactory.home.route(),
|
link: routes.home.route(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: routeFactory.productCategories.title,
|
title: routes.productCategories.title,
|
||||||
link: routeFactory.productCategories.route(),
|
link: routes.productCategories.route(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: routeFactory.projects.title,
|
title: routes.projects.title,
|
||||||
link: routeFactory.projects.route(),
|
link: routes.projects.route(),
|
||||||
},
|
},
|
||||||
] as any[];
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import routeFactory from '../routeFactory';
|
import routeFactory from '../routeFactory';
|
||||||
|
|
||||||
export const whoAreWeLinks = [
|
export function getWhoAreWeLinks(t: (key: string) => string, locale: string) {
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
return [
|
||||||
{
|
{
|
||||||
title: routeFactory.about.title,
|
title: routes.about.title,
|
||||||
link: routeFactory.about.route(),
|
link: routes.about.route(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: routeFactory.contact.title,
|
title: routes.contact.title,
|
||||||
link: routeFactory.contact.route(),
|
link: routes.contact.route(),
|
||||||
},
|
},
|
||||||
] as any[];
|
];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { footerMenu } from './footerMenu';
|
import footerMenu from './footerMenu';
|
||||||
import routeFactory from './routeFactory';
|
import routeFactory from './routeFactory';
|
||||||
import socials from './socials';
|
import socials from './socials';
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,45 @@
|
|||||||
import { t, translates } from '@/locales/translates';
|
type RouteFactory = (
|
||||||
|
t: (key: string) => string,
|
||||||
|
locale: string,
|
||||||
|
) => {
|
||||||
|
home: { title: string; route: () => string };
|
||||||
|
productCategories: { title: string; route: () => string };
|
||||||
|
products: { title: string; route: (productId: string) => string };
|
||||||
|
projects: { title: string; route: () => string };
|
||||||
|
about: { title: string; route: () => string };
|
||||||
|
blog: { title: string; route: () => string };
|
||||||
|
contact: { title: string; route: () => string };
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
const routeFactory: RouteFactory = (t, locale) => ({
|
||||||
home: {
|
home: {
|
||||||
title: t('pages_home_title') as string,
|
title: t('pages_home_title'),
|
||||||
route: () => '/',
|
route: () => `/${locale}`,
|
||||||
},
|
},
|
||||||
productCategories: {
|
productCategories: {
|
||||||
title: t('pages_product_categories_title') as string,
|
title: t('pages_product_categories_title'),
|
||||||
route: () => '/product-categories',
|
route: () => `/${locale}/product-categories`,
|
||||||
},
|
},
|
||||||
products: {
|
products: {
|
||||||
title: t('pages_products_title') as string,
|
title: t('pages_products_title'),
|
||||||
route: (productId: string) => `/product-categories/${productId}`,
|
route: (productId: string) => `/${locale}/product-categories/${productId}`,
|
||||||
},
|
},
|
||||||
projects: {
|
projects: {
|
||||||
title: t('pages_projects_title') as string,
|
title: t('pages_projects_title'),
|
||||||
route: () => '/projects',
|
route: () => `/${locale}/projects`,
|
||||||
},
|
},
|
||||||
about: {
|
about: {
|
||||||
title: t('pages_about_title') as string,
|
title: t('pages_about_title'),
|
||||||
route: () => '/about-us',
|
route: () => `/${locale}/about-us`,
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
title: t('pages_blog_title') as string,
|
title: t('pages_blog_title'),
|
||||||
route: () => '/blog-us',
|
route: () => `/${locale}/blog-us`,
|
||||||
},
|
},
|
||||||
contact: {
|
contact: {
|
||||||
title: t('pages_contact_title') as string,
|
title: t('pages_contact_title'),
|
||||||
route: () => '/contact',
|
route: () => `/${locale}/contact`,
|
||||||
},
|
},
|
||||||
};
|
});
|
||||||
|
|
||||||
|
export default routeFactory;
|
||||||
|
|||||||
@@ -1,13 +1,25 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import FooterMenuBuilder from './FooterMenuBuilder';
|
import FooterMenuBuilder from './FooterMenuBuilder';
|
||||||
import { appConstants } from '@/assets/constants';
|
|
||||||
import FooterMenuBuilderWrapper from './FooterMenuBuilderWrapper';
|
import FooterMenuBuilderWrapper from './FooterMenuBuilderWrapper';
|
||||||
import FooterInfo from './FooterInfo';
|
import FooterInfo from './FooterInfo';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
import { getQuickLinks } from '@/assets/constants/footerMenu/quickLinks.const';
|
||||||
|
import { getWhoAreWeLinks } from '@/assets/constants/footerMenu/whoAreWe.const';
|
||||||
|
import { TLocales } from '@/locales/translates';
|
||||||
|
|
||||||
export default function Footer() {
|
export default function Footer() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const params = useParams();
|
||||||
|
const locale = params.locale as TLocales;
|
||||||
|
|
||||||
|
const quickLinks = getQuickLinks(t, locale);
|
||||||
|
const whoAreWeLinks = getWhoAreWeLinks(t, locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full bg-gray-500 pt-24 text-white">
|
<div className="relative w-full bg-gray-500 pt-24 text-white">
|
||||||
<div className="absolute inset-0 z-10 flex items-end justify-start">
|
<div className="absolute inset-0 z-10 flex items-end justify-start">
|
||||||
@@ -21,28 +33,26 @@ export default function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="relative z-20 container mx-auto">
|
<div className="relative z-20 container mx-auto">
|
||||||
<div className="flex max-w-[920px] flex-col gap-5 pb-10">
|
<div className="flex max-w-[920px] flex-col gap-5 pb-10">
|
||||||
<p className="text-5xl tracking-tighter">
|
<p className="text-5xl tracking-tighter">{t('footer_title')}</p>
|
||||||
{t('footer_title') as string}
|
|
||||||
</p>
|
|
||||||
<h5 className="text-base tracking-normal">
|
<h5 className="text-base tracking-normal">
|
||||||
{t('footer_description') as string}
|
{t('footer_description')}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-start justify-between gap-8 border-y border-gray-300/50 py-14 max-lg:flex-col-reverse">
|
<div className="flex items-start justify-between gap-8 border-y border-gray-300/50 py-14 max-lg:flex-col-reverse">
|
||||||
<FooterInfo withLogo className="lg:max-w-[200px]" />
|
<FooterInfo withLogo className="lg:max-w-[200px]" />
|
||||||
<div className="grid w-full grid-cols-2 gap-5 md:grid-cols-3 md:gap-10 lg:max-w-3/5">
|
<div className="grid w-full grid-cols-2 gap-5 md:grid-cols-3 md:gap-10 lg:max-w-3/5">
|
||||||
<FooterMenuBuilder
|
<FooterMenuBuilder
|
||||||
title={t('footer_links_quick_links') as string}
|
title={t('footer_links_quick_links')}
|
||||||
links={appConstants.footerMenu.quickLinks}
|
links={quickLinks}
|
||||||
className="max-md:text-center"
|
className="max-md:text-center"
|
||||||
/>
|
/>
|
||||||
<FooterMenuBuilder
|
<FooterMenuBuilder
|
||||||
title={t('footer_links_who_we_are') as string}
|
title={t('footer_links_who_we_are')}
|
||||||
links={appConstants.footerMenu.whoAreWeLinks}
|
links={whoAreWeLinks}
|
||||||
className="max-md:text-center"
|
className="max-md:text-center"
|
||||||
/>
|
/>
|
||||||
<FooterMenuBuilderWrapper
|
<FooterMenuBuilderWrapper
|
||||||
title={t('footer_links_contact') as string}
|
title={t('footer_links_contact')}
|
||||||
className="max-md:col-span-2 max-md:text-center"
|
className="max-md:col-span-2 max-md:text-center"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
@@ -59,7 +69,7 @@ export default function Footer() {
|
|||||||
info@sample.com
|
info@sample.com
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-sm tracking-tight text-gray-100 hover:text-gray-200">
|
<span className="text-sm tracking-tight text-gray-100 hover:text-gray-200">
|
||||||
{t('footer_links_address') as string}
|
{t('footer_links_address')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</FooterMenuBuilderWrapper>
|
</FooterMenuBuilderWrapper>
|
||||||
@@ -67,9 +77,7 @@ export default function Footer() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="py-8 lg:py-14">
|
<div className="py-8 lg:py-14">
|
||||||
<p className="text-center text-sm">
|
<p className="text-center text-sm">{t('footer_copyright')}</p>
|
||||||
{t('footer_copyright') as string}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { appConstants } from '@/assets/constants';
|
import { appConstants } from '@/assets/constants';
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import { Icon, IconName } from '@/components/uikit/icons';
|
import { Icon, IconName } from '@/components/uikit/icons';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
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';
|
||||||
|
|
||||||
export default function FooterInfo({
|
export default function FooterInfo({
|
||||||
withLogo,
|
withLogo,
|
||||||
@@ -12,6 +14,8 @@ export default function FooterInfo({
|
|||||||
withLogo?: boolean;
|
withLogo?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`flex w-full flex-col gap-3 max-lg:text-center lg:w-auto ${className}`}
|
className={`flex w-full flex-col gap-3 max-lg:text-center lg:w-auto ${className}`}
|
||||||
@@ -26,7 +30,7 @@ export default function FooterInfo({
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="text-sm tracking-normal">{t('footer_slogan') as string}</p>
|
<p className="text-sm tracking-normal">{t('footer_slogan')}</p>
|
||||||
<div className="flex items-center gap-4 max-lg:justify-center">
|
<div className="flex items-center gap-4 max-lg:justify-center">
|
||||||
{appConstants.socials.map((social) => (
|
{appConstants.socials.map((social) => (
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import FooterMenuBuilderWrapper from './FooterMenuBuilderWrapper';
|
|||||||
|
|
||||||
export default function FooterMenuBuilder({
|
export default function FooterMenuBuilder({
|
||||||
title,
|
title,
|
||||||
links,
|
links = [],
|
||||||
className = '',
|
className = '',
|
||||||
}: {
|
}: {
|
||||||
title: string;
|
title: string;
|
||||||
links: { title: string; link: string }[];
|
links?: { title: string; link: string }[];
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname, useParams } from 'next/navigation';
|
||||||
import { Icon } from '@/components/uikit/icons';
|
import { Icon } from '@/components/uikit/icons';
|
||||||
import { t } from '@/locales/translates';
|
import { useTranslations } from 'next-intl';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
import FooterInfo from '../footer/FooterInfo';
|
import FooterInfo from '../footer/FooterInfo';
|
||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
@@ -15,12 +15,17 @@ interface Props {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const links = [routeFactory.home, routeFactory.about, routeFactory.contact];
|
|
||||||
|
|
||||||
export default function HamburgerMenu({ isOpen, onClose }: Props) {
|
export default function HamburgerMenu({ isOpen, onClose }: Props) {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const params = useParams();
|
||||||
|
const locale = params.locale as string;
|
||||||
|
const t = useTranslations();
|
||||||
const menuRef = useRef<HTMLDivElement>(null);
|
const menuRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Generate links dynamically with current locale and translations
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
const links = [routes.home, routes.about, routes.contact];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
function handleClickOutside(event: MouseEvent) {
|
function handleClickOutside(event: MouseEvent) {
|
||||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||||
@@ -50,7 +55,7 @@ export default function HamburgerMenu({ isOpen, onClose }: Props) {
|
|||||||
className="relative z-10 flex h-full w-80 max-w-full flex-col bg-black p-5 text-white"
|
className="relative z-10 flex h-full w-80 max-w-full flex-col bg-black p-5 text-white"
|
||||||
>
|
>
|
||||||
<div className="mb-14 flex items-center justify-between">
|
<div className="mb-14 flex items-center justify-between">
|
||||||
<Link href="/" className="h-12 w-auto">
|
<Link href={`/${locale}`} className="h-12 w-auto">
|
||||||
<Image
|
<Image
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={images.logo}
|
src={images.logo}
|
||||||
@@ -76,7 +81,7 @@ export default function HamburgerMenu({ isOpen, onClose }: Props) {
|
|||||||
: 'border border-white/10 text-white hover:bg-white/10'
|
: 'border border-white/10 text-white hover:bg-white/10'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{title as string}
|
{title}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import Navbar from '../navbar';
|
import Navbar from '../navbar';
|
||||||
import Button, { IconButton } from '@/components/uikit/button';
|
import Button, { IconButton } from '@/components/uikit/button';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import { Icon } from '@/components/uikit/icons';
|
import { Icon } from '@/components/uikit/icons';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
const bannerFooter = [
|
const bannerFooter = [
|
||||||
t('com_home_Banner_link1') as string,
|
t('com_home_Banner_link1'),
|
||||||
t('com_home_Banner_link2') as string,
|
t('com_home_Banner_link2'),
|
||||||
t('com_home_Banner_link3') as string,
|
t('com_home_Banner_link3'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
className="relative w-full bg-cover bg-right bg-no-repeat text-white md:h-screen"
|
className="relative w-full bg-cover bg-right bg-no-repeat text-white md:h-screen"
|
||||||
@@ -20,21 +25,19 @@ export default function Header() {
|
|||||||
<div className="relative z-10 container mx-auto flex h-[80%] flex-col items-center bg-cover bg-center pb-6 text-white md:justify-between md:py-20">
|
<div className="relative z-10 container mx-auto flex h-[80%] flex-col items-center bg-cover bg-center pb-6 text-white md:justify-between md:py-20">
|
||||||
<div className="flex w-full flex-col justify-start">
|
<div className="flex w-full flex-col justify-start">
|
||||||
<h1 className="text-xl font-light md:text-5xl lg:text-7xl">
|
<h1 className="text-xl font-light md:text-5xl lg:text-7xl">
|
||||||
{t('com_home_banner_title') as string}
|
{t('com_home_banner_title')}
|
||||||
</h1>
|
</h1>
|
||||||
<h1 className="text-xl font-bold md:text-5xl lg:text-7xl">
|
<h1 className="text-xl font-bold md:text-5xl lg:text-7xl">
|
||||||
{t('com_home_banner_title_bold') as string}
|
{t('com_home_banner_title_bold')}
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
className="mt-2 mb-8 text-xs font-normal md:mt-5 md:text-sm lg:text-base"
|
className="mt-2 mb-8 text-xs font-normal md:mt-5 md:text-sm lg:text-base"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: t('com_home_banner_description') as string,
|
__html: t('com_home_banner_description'),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<Button endIcon="showMore">
|
<Button endIcon="showMore">{t('com_home_banner_button')}</Button>
|
||||||
{t('com_home_banner_button') as string}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,113 +1,33 @@
|
|||||||
// 'use client';
|
|
||||||
|
|
||||||
// import images from '@/assets/images/images';
|
|
||||||
// import { Icon } from '@/components/uikit/icons';
|
|
||||||
// import { t } from '@/locales/translates';
|
|
||||||
// import Image from 'next/image';
|
|
||||||
// import Link from 'next/link';
|
|
||||||
// import { usePathname } from 'next/navigation';
|
|
||||||
// import { useState } from 'react';
|
|
||||||
|
|
||||||
// export default function Navbar() {
|
|
||||||
// const [open, setOpen] = useState(false);
|
|
||||||
// const pathname = usePathname();
|
|
||||||
|
|
||||||
// const links = [
|
|
||||||
// { href: '/', label: t('pages_home_title') as string },
|
|
||||||
// { href: '/about', label: t('pages_about_title') as string },
|
|
||||||
// { href: '/blog', label: t('pages_contact_title') as string },
|
|
||||||
// { href: '/contact', label: t('pages_contact_title') as string },
|
|
||||||
// { href: '/product', label: t('pages_product_categories_title') as string },
|
|
||||||
// { href: '/project', label: t('pages_projects_title') as string },
|
|
||||||
// ];
|
|
||||||
|
|
||||||
// const linkClass = (href: string) =>
|
|
||||||
// pathname === href ? ' text-red-500 font-semibold' : 'text-white';
|
|
||||||
|
|
||||||
// return (
|
|
||||||
// <nav className="relative z-10 container mx-auto shrink-0 bg-transparent pt-7 pb-3 md:py-11">
|
|
||||||
// <div className="flex items-center justify-between border-b-0 border-white/10 pb-6 md:border-b-[1px]">
|
|
||||||
// <Link href="/" className="h-12 w-auto">
|
|
||||||
// <Image
|
|
||||||
// alt="logo"
|
|
||||||
// src={images.logo}
|
|
||||||
// className="h-full w-auto object-contain"
|
|
||||||
// />
|
|
||||||
// </Link>
|
|
||||||
// <ul className="hidden space-x-6 text-sm md:flex lg:text-base">
|
|
||||||
// {links.map(({ href, label }) => (
|
|
||||||
// <li key={href}>
|
|
||||||
// <Link
|
|
||||||
// href={href}
|
|
||||||
// className={`${linkClass(href)} hover:text-primary-light`}
|
|
||||||
// >
|
|
||||||
// {label}
|
|
||||||
// </Link>
|
|
||||||
// </li>
|
|
||||||
// ))}
|
|
||||||
// <li>
|
|
||||||
// <a className="flex items-center gap-2" href="tel:+098123456789">
|
|
||||||
// <Icon name="phone" className="text-primary" />
|
|
||||||
// <p>+098 123456789</p>
|
|
||||||
// </a>
|
|
||||||
// </li>
|
|
||||||
// </ul>
|
|
||||||
|
|
||||||
// <button
|
|
||||||
// className="text-gray-700 md:hidden"
|
|
||||||
// onClick={() => setOpen(!open)}
|
|
||||||
// >
|
|
||||||
// {open ? (
|
|
||||||
// <Icon name="close" className="text-white" />
|
|
||||||
// ) : (
|
|
||||||
// <Icon name="menu" className="text-white" />
|
|
||||||
// )}
|
|
||||||
// </button>
|
|
||||||
// </div>
|
|
||||||
|
|
||||||
// {open && (
|
|
||||||
// <ul className="mt-2 space-y-2 px-2 md:hidden">
|
|
||||||
// {links.map(({ href, label }) => (
|
|
||||||
// <li key={href}>
|
|
||||||
// <Link
|
|
||||||
// href={href}
|
|
||||||
// className={linkClass(href)}
|
|
||||||
// onClick={() => setOpen(false)}
|
|
||||||
// >
|
|
||||||
// {label}
|
|
||||||
// </Link>
|
|
||||||
// </li>
|
|
||||||
// ))}
|
|
||||||
// </ul>
|
|
||||||
// )}
|
|
||||||
// </nav>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import { Icon } from '@/components/uikit/icons';
|
import { Icon } 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 { usePathname } from 'next/navigation';
|
import { usePathname, useParams } from 'next/navigation';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import HamburgerMenu from '../hamburgerMenu';
|
import HamburgerMenu from '../hamburgerMenu';
|
||||||
import { t } from '@/locales/translates';
|
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(false);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const params = useParams();
|
||||||
|
const locale = params.locale as string;
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
// Get localized routes
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
const links = [
|
const links = [
|
||||||
routeFactory.home,
|
routes.home,
|
||||||
routeFactory.productCategories,
|
routes.productCategories,
|
||||||
routeFactory.projects,
|
routes.projects,
|
||||||
routeFactory.blog,
|
routes.blog,
|
||||||
routeFactory.about,
|
routes.about,
|
||||||
routeFactory.contact,
|
routes.contact,
|
||||||
];
|
];
|
||||||
|
|
||||||
const linkClass = (href: string) =>
|
const linkClass = (href: string) =>
|
||||||
pathname === href ? 'text-primary font-semibold' : 'text-white';
|
pathname === href ? 'text-primary font-semibold' : 'text-white';
|
||||||
|
|
||||||
@@ -115,7 +35,7 @@ export default function Navbar() {
|
|||||||
<>
|
<>
|
||||||
<nav className="relative z-50 container mx-auto bg-transparent pt-7 pb-3 md:py-11">
|
<nav className="relative z-50 container mx-auto bg-transparent pt-7 pb-3 md:py-11">
|
||||||
<div className="flex items-center justify-between border-b-0 border-white/10 pb-6 md:border-b">
|
<div className="flex items-center justify-between border-b-0 border-white/10 pb-6 md:border-b">
|
||||||
<Link href="/" className="h-12 w-auto">
|
<Link href={`/${locale}`} className="h-12 w-auto">
|
||||||
<Image
|
<Image
|
||||||
alt="logo"
|
alt="logo"
|
||||||
src={images.logo}
|
src={images.logo}
|
||||||
|
|||||||
@@ -1,16 +1,24 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
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 AchievementTextBox from '@/components/layout/achievementTextBox';
|
import AchievementTextBox from '@/components/layout/achievementTextBox';
|
||||||
import SectionTitle from '@/components/layout/sectionTitle';
|
import SectionTitle from '@/components/layout/sectionTitle';
|
||||||
import Button from '@/components/uikit/button';
|
import Button from '@/components/uikit/button';
|
||||||
import { t } from '@/locales/translates';
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
export default function AboutTopInfo() {
|
export default function AboutTopInfo() {
|
||||||
|
const t = useTranslations();
|
||||||
|
const params = useParams();
|
||||||
|
const locale = params.locale as string;
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
|
||||||
const topInfoAchievementTitles = [
|
const topInfoAchievementTitles = [
|
||||||
t('com_about_info_achievements_1') as string,
|
t('com_about_info_achievements_1'),
|
||||||
t('com_about_info_achievements_2') as string,
|
t('com_about_info_achievements_2'),
|
||||||
t('com_about_info_achievements_3') as string,
|
t('com_about_info_achievements_3'),
|
||||||
t('com_about_info_achievements_4') as string,
|
t('com_about_info_achievements_4'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -30,7 +38,7 @@ export default function AboutTopInfo() {
|
|||||||
<div className="flex items-center justify-center gap-5 pb-5">
|
<div className="flex items-center justify-center gap-5 pb-5">
|
||||||
<span className="text-primary text-5xl">+25</span>
|
<span className="text-primary text-5xl">+25</span>
|
||||||
<span className="text-base text-gray-300">
|
<span className="text-base text-gray-300">
|
||||||
{t('com_about_info_years_experience') as string}
|
{t('com_about_info_years_experience')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,14 +53,14 @@ export default function AboutTopInfo() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col pt-16 pb-20">
|
<div className="flex flex-col pt-16 pb-20">
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
title={t('pages_about_title') as string}
|
title={t('pages_about_title')}
|
||||||
description={t('com_about_info_title') as string}
|
description={t('com_about_info_title')}
|
||||||
description_bold={t('com_about_info_title_bold') as string}
|
description_bold={t('com_about_info_title_bold')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mt-5">
|
<div className="mt-5">
|
||||||
<p className="text-base font-light text-gray-300">
|
<p className="text-base font-light text-gray-300">
|
||||||
{t('com_about_info_description') as string}
|
{t('com_about_info_description')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10 flex flex-col gap-4">
|
<div className="mt-10 flex flex-col gap-4">
|
||||||
@@ -61,8 +69,8 @@ export default function AboutTopInfo() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10">
|
<div className="mt-10">
|
||||||
<Button endIcon="showMore" link={routeFactory.contact.route()}>
|
<Button endIcon="showMore" link={routes.contact.route()}>
|
||||||
{t('pages_contact_title') as string}
|
{t('pages_contact_title')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import SectionTitle from '@/components/layout/sectionTitle';
|
import SectionTitle from '@/components/layout/sectionTitle';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export default function HomeStorySection({
|
export default function HomeStorySection({
|
||||||
className = '',
|
className = '',
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
const statisticsData = [
|
const statisticsData = [
|
||||||
{
|
{
|
||||||
title: t('com_home_story_projects_count') as string,
|
title: t('com_home_story_projects_count'),
|
||||||
count: '10K',
|
count: '10K',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('com_home_story_customer_count') as string,
|
title: t('com_home_story_customer_count'),
|
||||||
count: '500',
|
count: '500',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('com_home_story_experience') as string,
|
title: t('com_home_story_experience'),
|
||||||
count: '25',
|
count: '25',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -27,9 +31,9 @@ export default function HomeStorySection({
|
|||||||
<div className={`container mx-auto flex flex-col gap-12 ${className}`}>
|
<div className={`container mx-auto flex flex-col gap-12 ${className}`}>
|
||||||
<div className="grid grid-cols-2 gap-10">
|
<div className="grid grid-cols-2 gap-10">
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
title={t('com_home_story_title') as string}
|
title={t('com_home_story_title')}
|
||||||
description={t('com_home_story_slogan') as string}
|
description={t('com_home_story_slogan')}
|
||||||
description_bold={t('com_home_story_slogan_bold') as string}
|
description_bold={t('com_home_story_slogan_bold')}
|
||||||
/>
|
/>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
{[images.homeBanner, images.aboutImg_1].map((e, i) => (
|
{[images.homeBanner, images.aboutImg_1].map((e, i) => (
|
||||||
@@ -53,7 +57,7 @@ export default function HomeStorySection({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col py-8">
|
<div className="flex flex-col py-8">
|
||||||
<p className="text-base font-light text-gray-500">
|
<p className="text-base font-light text-gray-500">
|
||||||
{t('com_home_story_description') as string}
|
{t('com_home_story_description')}
|
||||||
</p>
|
</p>
|
||||||
<hr className="my-10 text-gray-100/50" />
|
<hr className="my-10 text-gray-100/50" />
|
||||||
<div className="grid grid-cols-3 justify-between gap-1">
|
<div className="grid grid-cols-3 justify-between gap-1">
|
||||||
|
|||||||
@@ -1,14 +1,22 @@
|
|||||||
import images from '@/assets/images/images';
|
import images from '@/assets/images/images';
|
||||||
import SectionTitle from '@/components/layout/sectionTitle';
|
import SectionTitle from '@/components/layout/sectionTitle';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import type { IProductCategoryCardProps } from '../productCategories/productCategoryCard.d';
|
import type { IProductCategoryCardProps } from '../productCategories/productCategoryCard.d';
|
||||||
import { getProductCategories } from '@/services/products';
|
import { getProductCategories } from '@/services/products';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
import { ILayoutLocaleParams } from '@/models/layout';
|
||||||
|
import { TLocales } from '@/locales/translates';
|
||||||
|
|
||||||
export default async function HomePageProducts() {
|
interface Props {
|
||||||
|
locale: TLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function HomePageProducts({ locale }: Props) {
|
||||||
|
const t = await getTranslations({ locale });
|
||||||
const productCategories: IProductCategoryCardProps[] =
|
const productCategories: IProductCategoryCardProps[] =
|
||||||
await getProductCategories();
|
await getProductCategories();
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
@@ -19,12 +27,12 @@ export default async function HomePageProducts() {
|
|||||||
<div className="absolute inset-0 z-10 bg-black opacity-50" />
|
<div className="absolute inset-0 z-10 bg-black opacity-50" />
|
||||||
<div className="relative z-20 container mx-auto grid grid-cols-2 gap-28">
|
<div className="relative z-20 container mx-auto grid grid-cols-2 gap-28">
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
title={t('pages_product_categories_title') as string}
|
title={t('pages_product_categories_title')}
|
||||||
description={t('com_home_products_title') as string}
|
description={t('com_home_products_title')}
|
||||||
reverseColor
|
reverseColor
|
||||||
/>
|
/>
|
||||||
<h4 className="pt-10 text-base font-extralight tracking-normal text-white">
|
<h4 className="pt-10 text-base font-extralight tracking-normal text-white">
|
||||||
{t('com_home_products_description') as string}
|
{t('com_home_products_description')}
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,16 +40,17 @@ export default async function HomePageProducts() {
|
|||||||
<div className="relative top-[-100px] z-20 container mx-auto w-full">
|
<div className="relative top-[-100px] z-20 container mx-auto w-full">
|
||||||
<div className="grid grid-cols-1 gap-8 rounded-4xl bg-gray-50 py-10 shadow-2xl md:grid-cols-2 lg:grid-cols-4">
|
<div className="grid grid-cols-1 gap-8 rounded-4xl bg-gray-50 py-10 shadow-2xl md:grid-cols-2 lg:grid-cols-4">
|
||||||
{productCategories.map((category, index) => (
|
{productCategories.map((category, index) => (
|
||||||
<Link href={routeFactory.products.route(category.id)}>
|
<Link key={category.id} href={routes.products.route(category.id)}>
|
||||||
<div
|
<div
|
||||||
key={index}
|
|
||||||
className={`flex flex-col items-center justify-center gap-4 p-6 text-center ${index ? 'border-s-[1px] border-gray-100' : ''}`}
|
className={`flex flex-col items-center justify-center gap-4 p-6 text-center ${index ? 'border-s-[1px] border-gray-100' : ''}`}
|
||||||
>
|
>
|
||||||
<h3 className="text-xl font-bold text-gray-500">
|
<h3 className="text-xl font-bold text-gray-500">
|
||||||
{category.title}
|
{category.title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-lg font-light text-gray-400">
|
<p className="text-lg font-light text-gray-400">
|
||||||
{`Includes ${category.typesCount} Products`}
|
{t('com_products_category_types_count', {
|
||||||
|
count: category.typesCount,
|
||||||
|
})}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import { Icon } from '@/components/uikit/icons';
|
import { Icon } from '@/components/uikit/icons';
|
||||||
import type { IProductCategoryCardProps } from './productCategoryCard.d';
|
import type { IProductCategoryCardProps } from './productCategoryCard.d';
|
||||||
import AchievementTextBox from '@/components/layout/achievementTextBox';
|
import AchievementTextBox from '@/components/layout/achievementTextBox';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import Button from '@/components/uikit/button';
|
import Button from '@/components/uikit/button';
|
||||||
import routeFactory from '@/assets/constants/routeFactory';
|
import routeFactory from '@/assets/constants/routeFactory';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
import { useParams } from 'next/navigation';
|
||||||
|
|
||||||
export default function ProductsCategoryCard({
|
export default function ProductsCategoryCard({
|
||||||
icon,
|
icon,
|
||||||
@@ -13,6 +16,11 @@ export default function ProductsCategoryCard({
|
|||||||
bestForTitle,
|
bestForTitle,
|
||||||
id,
|
id,
|
||||||
}: IProductCategoryCardProps) {
|
}: IProductCategoryCardProps) {
|
||||||
|
const t = useTranslations();
|
||||||
|
const params = useParams();
|
||||||
|
const locale = params.locale as string;
|
||||||
|
const routes = routeFactory(t, locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-hidden rounded-4xl bg-gray-100 p-3 sm:p-6 md:p-10">
|
<div className="relative overflow-hidden rounded-4xl bg-gray-100 p-3 sm:p-6 md:p-10">
|
||||||
<div className="absolute -end-2 -top-2">
|
<div className="absolute -end-2 -top-2">
|
||||||
@@ -22,22 +30,18 @@ export default function ProductsCategoryCard({
|
|||||||
<div className="flex flex-col gap-4 pt-32">
|
<div className="flex flex-col gap-4 pt-32">
|
||||||
<h2 className="text-xl font-semibold text-gray-500">{title}</h2>
|
<h2 className="text-xl font-semibold text-gray-500">{title}</h2>
|
||||||
<AchievementTextBox
|
<AchievementTextBox
|
||||||
title={
|
title={t('com_products_category_types_count', { count: typesCount })}
|
||||||
t('com_products_category_types_count', {
|
|
||||||
count: typesCount,
|
|
||||||
}) as string
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<AchievementTextBox title={priceTypeTitle} />
|
<AchievementTextBox title={priceTypeTitle} />
|
||||||
<AchievementTextBox title={bestForTitle} />
|
<AchievementTextBox title={bestForTitle} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-24 w-auto">
|
<div className="mt-24 w-auto">
|
||||||
<Button
|
<Button
|
||||||
link={routeFactory.products.route(id)}
|
link={routes.products.route(id)}
|
||||||
endIcon="showMore"
|
endIcon="showMore"
|
||||||
className="w-fit"
|
className="w-fit"
|
||||||
>
|
>
|
||||||
{t('com_products_category_show_all_CTA') as string}
|
{t('com_products_category_show_all_CTA')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
import AchievementTextBox from '@/components/layout/achievementTextBox';
|
import AchievementTextBox from '@/components/layout/achievementTextBox';
|
||||||
import type { IProductCategoryCardProps } from './productCategoryCard.d';
|
import type { IProductCategoryCardProps } from './productCategoryCard.d';
|
||||||
import { t } from '@/locales/translates';
|
|
||||||
import { Icon } from '@/components/uikit/icons';
|
import { Icon } from '@/components/uikit/icons';
|
||||||
|
import { useTranslations } from 'next-intl';
|
||||||
|
|
||||||
export default function ProductCategoryCardInProducts({
|
export default function ProductCategoryCardInProducts({
|
||||||
productCategory,
|
productCategory,
|
||||||
@@ -10,6 +12,8 @@ export default function ProductCategoryCardInProducts({
|
|||||||
productCategory: IProductCategoryCardProps;
|
productCategory: IProductCategoryCardProps;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="">
|
<div className="">
|
||||||
<div
|
<div
|
||||||
@@ -23,19 +27,15 @@ export default function ProductCategoryCardInProducts({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col gap-5">
|
<div className="flex flex-col gap-5">
|
||||||
<h1 className="text-2xl font-semibold text-white">
|
<h1 className="text-2xl font-semibold text-white">
|
||||||
{
|
{t('com_products_products_category_title', {
|
||||||
t('com_products_products_category_title', {
|
|
||||||
title: productCategory.title,
|
title: productCategory.title,
|
||||||
}) as string
|
})}
|
||||||
}
|
|
||||||
</h1>
|
</h1>
|
||||||
<AchievementTextBox
|
<AchievementTextBox
|
||||||
reverseColor
|
reverseColor
|
||||||
title={
|
title={t('com_products_category_types_count', {
|
||||||
t('com_products_category_types_count', {
|
|
||||||
count: productCategory.typesCount,
|
count: productCategory.typesCount,
|
||||||
}) as string
|
})}
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<AchievementTextBox
|
<AchievementTextBox
|
||||||
reverseColor
|
reverseColor
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export const port = process.env.PORT || 3000;
|
||||||
|
export const host = process.env.VERCEL_PROJECT_PRODUCTION_URL
|
||||||
|
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
|
||||||
|
: `http://localhost:${port}`;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { createNavigation } from 'next-intl/navigation';
|
||||||
|
import { routing } from './routing';
|
||||||
|
|
||||||
|
export const { Link, getPathname, redirect, usePathname, useRouter } =
|
||||||
|
createNavigation(routing);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { hasLocale } from 'next-intl';
|
||||||
|
import { getRequestConfig } from 'next-intl/server';
|
||||||
|
import { routing } from './routing';
|
||||||
|
|
||||||
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
|
// Typically corresponds to the `[locale]` segment
|
||||||
|
const requested = await requestLocale;
|
||||||
|
const locale = hasLocale(routing.locales, requested)
|
||||||
|
? requested
|
||||||
|
: routing.defaultLocale;
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
messages: (await import(`../../messages/${locale}.json`)).default,
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { defineRouting } from 'next-intl/routing';
|
||||||
|
|
||||||
|
export const routing = defineRouting({
|
||||||
|
locales: ['en', 'fa'],
|
||||||
|
defaultLocale: 'en',
|
||||||
|
pathnames: {
|
||||||
|
'/': '/',
|
||||||
|
'/pathnames': {
|
||||||
|
fa: '/pfadnamen',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -58,7 +58,7 @@ export const translates = (
|
|||||||
export const t = (
|
export const t = (
|
||||||
key: keyof Translates,
|
key: keyof Translates,
|
||||||
dynamicValue?: Record<string, string | number>,
|
dynamicValue?: Record<string, string | number>,
|
||||||
) => translates(key, dynamicValue);
|
) => '';
|
||||||
|
|
||||||
export type TLocales = 'en' | 'fa';
|
export type TLocales = 'en' | 'fa';
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import createMiddleware from 'next-intl/middleware';
|
||||||
|
import { routing } from './i18n/routing';
|
||||||
|
|
||||||
|
export default createMiddleware(routing);
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
|
||||||
|
};
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
export interface ILayoutLocaleParams {
|
||||||
|
locale: TLocales;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user