feat: add product generation and pagination components
- Implemented product generation for categories with a helper function to create 50 products per category. - Added image assets for English and Persian language support. - Created a reusable Pagination component for navigating through product lists. - Developed ProductList component to display products with pagination support. - Defined TypeScript interfaces for product list props and API response structure.
This commit is contained in:
@@ -15,7 +15,7 @@ export default async function RootLayout({
|
||||
children: React.ReactNode;
|
||||
params: ILayoutLocaleParams;
|
||||
}>) {
|
||||
const locale = params.locale;
|
||||
const locale = await params.locale;
|
||||
let messages;
|
||||
try {
|
||||
messages = (await import(`../../../messages/${locale}.json`)).default;
|
||||
|
||||
@@ -2,13 +2,10 @@ import Footer from '@/components/layout/footer/Footer';
|
||||
import '@/app/globals.css';
|
||||
import 'leaflet/dist/leaflet.css';
|
||||
import { notFound } from 'next/navigation';
|
||||
// import { cookies } from 'next/headers';
|
||||
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',
|
||||
// };
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export default async function RootLayout({
|
||||
children,
|
||||
@@ -17,7 +14,12 @@ export default async function RootLayout({
|
||||
children: React.ReactNode;
|
||||
params: ILayoutLocaleParams;
|
||||
}>) {
|
||||
const locale = params.locale;
|
||||
const paramsData = await params;
|
||||
const locale = paramsData.locale;
|
||||
// const cookieStore = await cookies();
|
||||
|
||||
// // Set locale cookie on the server
|
||||
// cookieStore.set('locale', locale, { path: '/', httpOnly: false });
|
||||
|
||||
const fontClass = locale === 'fa' ? 'font-fa' : 'font-en';
|
||||
|
||||
|
||||
@@ -3,8 +3,10 @@ import images from '@/assets/images/images';
|
||||
import Breadcrumb from '@/components/layout/breadcrumb';
|
||||
import { IBreadcrumbItem } from '@/components/layout/breadcrumb/breadcrumb';
|
||||
import InnerPageBanner from '@/components/layout/innerPages/banner';
|
||||
import Pagination from '@/components/layout/pagination';
|
||||
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 { getProductCategoryById, getProducts } from '@/services/products';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
@@ -33,6 +35,8 @@ export default async function ProductPage({
|
||||
{ title: productCategory.title },
|
||||
] as IBreadcrumbItem[];
|
||||
|
||||
const refetch = () => {};
|
||||
|
||||
return (
|
||||
<main className="">
|
||||
<InnerPageBanner
|
||||
@@ -49,11 +53,11 @@ export default async function ProductPage({
|
||||
<h2 className="mb-6 text-3xl font-semibold text-gray-500">
|
||||
{productCategory.title} Products
|
||||
</h2>
|
||||
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
|
||||
{products.map((product, index) => (
|
||||
<ProductCard {...product} key={index} />
|
||||
))}
|
||||
</div>
|
||||
<ProductList
|
||||
initialProducts={products.data}
|
||||
initialPagination={products.pagination}
|
||||
categoryId={productCategoryId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { productCategories } from '../../data';
|
||||
import type { IProductCardProps } from '@/components/pages/products/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 type { NextApiRequestContext } from 'next';
|
||||
|
||||
import { cookies } from 'next/headers';
|
||||
import { productCategories } from '../../data';
|
||||
import images from '@/assets/images/images';
|
||||
import { generateProductsForCategory } from './data';
|
||||
|
||||
export async function GET(request: NextRequest, context: any) {
|
||||
const cookieStore = await cookies();
|
||||
const lang = cookieStore.get('lang')?.value || 'en';
|
||||
const lang = cookieStore.get('locale')?.value || 'en';
|
||||
|
||||
const category = productCategories.find(
|
||||
(cat) => cat.id === context.params.id,
|
||||
);
|
||||
const params = context.params;
|
||||
const category = productCategories.find((cat) => cat.id === params.id);
|
||||
|
||||
if (!category) {
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
const product = {
|
||||
title: 'Product title',
|
||||
size: 'W 18 * L10',
|
||||
imageSrc: '/product1.png',
|
||||
};
|
||||
const products = Array.from({ length: 10 }, () => product);
|
||||
|
||||
// Pagination logic
|
||||
const { searchParams } = new URL(request.url);
|
||||
const page = parseInt(searchParams.get('page') || '1', 10);
|
||||
const perPage = 20;
|
||||
|
||||
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({
|
||||
data: products,
|
||||
data: paginatedProducts,
|
||||
pagination: {
|
||||
page,
|
||||
perPage,
|
||||
total: allProducts.length,
|
||||
totalPages: Math.ceil(allProducts.length / perPage),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,45 +1,37 @@
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
// import type { NextApiRequestContext } from 'next';
|
||||
'use server';
|
||||
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { productCategories } from '../data';
|
||||
|
||||
export async function GET(request: NextRequest, context: any) {
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
context: { params: { id: string } },
|
||||
) {
|
||||
const cookieStore = await cookies();
|
||||
const lang = cookieStore.get('lang')?.value || 'en';
|
||||
const lang = cookieStore.get('locale')?.value || 'en';
|
||||
|
||||
const category = productCategories.find(
|
||||
(cat) => cat.id === context.params.id,
|
||||
);
|
||||
const params = await context.params;
|
||||
|
||||
const { id } = params;
|
||||
|
||||
let category = productCategories.find((cat) => cat.id === id);
|
||||
|
||||
if (!category) {
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const titleData =
|
||||
lang === 'fa' && category.fa_title ? category.fa_title : category.title;
|
||||
const priceTypeTitleData =
|
||||
lang === 'fa' && category.fa_priceTypeTitle
|
||||
? category.fa_priceTypeTitle
|
||||
: category.priceTypeTitle;
|
||||
const bestForTitleData =
|
||||
lang === 'fa' && category.fa_bestForTitle
|
||||
? category.fa_bestForTitle
|
||||
: category.bestForTitle;
|
||||
const descriptionData =
|
||||
lang === 'fa' && category.fa_description
|
||||
? category.fa_description
|
||||
: category.description;
|
||||
if (lang === 'fa') {
|
||||
category = {
|
||||
...category,
|
||||
title: category.fa_title || category.title,
|
||||
priceTypeTitle: category.fa_priceTypeTitle || category.priceTypeTitle,
|
||||
bestForTitle: category.fa_bestForTitle || category.bestForTitle,
|
||||
description: category.fa_description || category.description,
|
||||
};
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
data: {
|
||||
id: category.id,
|
||||
icon: category.icon,
|
||||
typesCount: category.typesCount,
|
||||
title: titleData,
|
||||
priceTypeTitle: priceTypeTitleData,
|
||||
bestForTitle: bestForTitleData,
|
||||
description: descriptionData,
|
||||
},
|
||||
data: category,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -23,23 +23,25 @@ export async function GET(request: Request) {
|
||||
description,
|
||||
fa_description,
|
||||
}) => {
|
||||
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;
|
||||
const descriptionData =
|
||||
lang === 'fa' && fa_description ? fa_description : description;
|
||||
if (lang === 'fa') {
|
||||
return {
|
||||
id,
|
||||
icon,
|
||||
typesCount,
|
||||
title: fa_title || title,
|
||||
priceTypeTitle: fa_priceTypeTitle || priceTypeTitle,
|
||||
bestForTitle: fa_bestForTitle || bestForTitle,
|
||||
description: fa_description || description,
|
||||
};
|
||||
}
|
||||
return {
|
||||
id,
|
||||
icon,
|
||||
typesCount,
|
||||
title: titleData,
|
||||
priceTypeTitle: priceTypeTitleData,
|
||||
bestForTitle: bestForTitleData,
|
||||
description: descriptionData,
|
||||
title,
|
||||
priceTypeTitle,
|
||||
bestForTitle,
|
||||
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 });
|
||||
}
|
||||
@@ -6,6 +6,11 @@
|
||||
|
||||
html{
|
||||
font-size: 16px;
|
||||
scroll-snap-stop: always;
|
||||
}
|
||||
|
||||
body{
|
||||
scroll-snap-stop: always;
|
||||
}
|
||||
|
||||
@media (width <=48rem) {
|
||||
|
||||
@@ -7,17 +7,19 @@ import aboutImg_1 from './about-img-1.png';
|
||||
import aboutImg_2 from './about-img-2.png';
|
||||
import homeProductBack from './home_product_back.jpeg';
|
||||
import whatWeDoImage from './what-we-do-image.png';
|
||||
import blogImg_1 from './blog_img_1.jpeg'
|
||||
import projectImg_1 from './project_img_1.jpeg'
|
||||
import blogImg_1 from './blog_img_1.jpeg';
|
||||
import projectImg_1 from './project_img_1.jpeg';
|
||||
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 {
|
||||
homeBanner,
|
||||
logo,
|
||||
aboutBanner,
|
||||
blogImg_1,
|
||||
blogBanner,
|
||||
projectImg_1,
|
||||
projectImg_1,
|
||||
factoryVector,
|
||||
aboutImg_1,
|
||||
aboutImg_2,
|
||||
@@ -25,4 +27,6 @@ export default {
|
||||
homeProductBack,
|
||||
whatWeDoImage,
|
||||
product1,
|
||||
langEn,
|
||||
langFa,
|
||||
};
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1020 B |
@@ -1,28 +1,49 @@
|
||||
'use client';
|
||||
|
||||
import Link from 'next/link';
|
||||
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 pathWithoutLocale = pathname.replace(/^\/(en|fa)/, '');
|
||||
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">
|
||||
<Link
|
||||
href={`/en${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`}
|
||||
className="mx-2 text-gray-800 hover:text-blue-500"
|
||||
>
|
||||
EN
|
||||
</Link>
|
||||
<span className="text-gray-800">|</span>
|
||||
<Link
|
||||
href={`/fa${pathWithoutLocale === '/' ? '' : pathWithoutLocale}`}
|
||||
className="mx-2 text-gray-800 hover:text-blue-500"
|
||||
>
|
||||
FA
|
||||
</Link>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { useTranslations } from 'next-intl';
|
||||
import routeFactory from '@/assets/constants/routeFactory';
|
||||
|
||||
export default function Navbar() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const pathname = usePathname();
|
||||
const params = useParams();
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import ProductCard from '../ProductCard';
|
||||
import Pagination from '@/components/layout/pagination';
|
||||
import { getProducts } from '@/services/products';
|
||||
import { IProductListProps } from './productList';
|
||||
|
||||
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';
|
||||
|
||||
export interface IProductListProps {
|
||||
initialProducts: IProductCardProps[];
|
||||
initialPagination: IPagination;
|
||||
categoryId: string;
|
||||
}
|
||||
+1
@@ -8,6 +8,7 @@ export interface IBaseButtonProps
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
link?: URL | string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export interface ITextButtonProps extends IBaseButtonProps {
|
||||
|
||||
@@ -11,6 +11,7 @@ const Button: React.FC<ITextButtonProps> = ({
|
||||
endIcon,
|
||||
className,
|
||||
link,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
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>
|
||||
</Link>
|
||||
) : (
|
||||
<button disabled={disabled || loading} {...props} className={wrapperClass}>
|
||||
<button
|
||||
disabled={disabled || loading}
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
className={wrapperClass}
|
||||
>
|
||||
<ButtonBody {...props} loading={loading} endIcon={endIcon}>
|
||||
{children}
|
||||
</ButtonBody>
|
||||
@@ -82,6 +88,7 @@ export const IconButton: React.FC<IIconButtonProps> = ({
|
||||
size = 'medium',
|
||||
color = 'primary',
|
||||
className,
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
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 || ''} `}
|
||||
disabled={disabled || loading}
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
>
|
||||
{loading ? (
|
||||
<svg
|
||||
|
||||
+4
-16
@@ -1,30 +1,18 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// Create an Axios instance
|
||||
const api = axios.create({
|
||||
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(
|
||||
(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;
|
||||
},
|
||||
(error) => Promise.reject(error),
|
||||
);
|
||||
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
export default api;
|
||||
|
||||
+39
-4
@@ -1,8 +1,43 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
import { routing } from './i18n/routing';
|
||||
import { NextResponse } from 'next/server';
|
||||
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 = {
|
||||
matcher: '/((?!api|trpc|_next|_vercel|.*\\..*).*)',
|
||||
matcher: ['/((?!_next/static|_next/image|favicon.ico|api).*)'],
|
||||
};
|
||||
|
||||
Vendored
+11
@@ -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 @@
|
||||
'use server';
|
||||
|
||||
import type { IProductCategoryCardProps } from '@/components/pages/productCategories/productCategoryCard.d';
|
||||
import type { IProductCardProps } from '@/components/pages/products/productCard.d';
|
||||
import api from '@/lib/axios';
|
||||
import { IResponse } from '@/models/response';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
export function getProductCategories() {
|
||||
export async function getProductCategories() {
|
||||
const cookiesStore = await cookies();
|
||||
return api
|
||||
.get('/product_categories')
|
||||
.get('/product_categories', {
|
||||
headers: {
|
||||
Cookie: cookiesStore.toString(),
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
return Promise.resolve(data.data);
|
||||
})
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
|
||||
export function getProductCategoryById(
|
||||
export async function getProductCategoryById(
|
||||
categoryId: string,
|
||||
): Promise<IProductCategoryCardProps> {
|
||||
const cookiesStore = await cookies();
|
||||
return api
|
||||
.get(`/product_categories/${categoryId}`)
|
||||
.get(`/product_categories/${categoryId}`, {
|
||||
headers: {
|
||||
Cookie: cookiesStore.toString(),
|
||||
},
|
||||
})
|
||||
.then(({ data }) => {
|
||||
return Promise.resolve(data.data);
|
||||
})
|
||||
.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
|
||||
.get(`/product_categories/${categoryId}/products`)
|
||||
.get(`/product_categories/${categoryId}/products`, {
|
||||
headers: {
|
||||
Cookie: cookiesStore.toString(),
|
||||
},
|
||||
params: { page },
|
||||
})
|
||||
.then(({ data }) => {
|
||||
return Promise.resolve(data.data);
|
||||
return Promise.resolve(data);
|
||||
})
|
||||
.catch((error) => Promise.reject(error));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user