Horizontal scrolling in mobile mode for projects and blogs and add favicon

This commit is contained in:
zahravaziri
2025-06-29 15:46:44 +03:30
parent edaea7e1e0
commit cef9c3bbc1
25 changed files with 313 additions and 125 deletions
+6 -2
View File
@@ -18,7 +18,9 @@ export async function generateMetadata({ params }: { params: IPageParams }) {
export default async function Blog({ params }: { params: IPageParams }) {
const paramsData = await params;
const t = await getTranslations({ locale: paramsData.locale });
const { locale } = paramsData;
const t = await getTranslations({ locale });
const { data: posts, pagination } = await getPosts();
@@ -28,7 +30,9 @@ export default async function Blog({ params }: { params: IPageParams }) {
title={t('pages_blog_title')}
imageSrc={images.blogBanner.src}
/>
<PostsGrid initialPosts={posts} initialPagination={pagination} />
<div id="postList" className="w-full">
<PostsGrid initialPosts={posts} initialPagination={pagination} />
</div>
</main>
);
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

+16 -1
View File
@@ -57,7 +57,22 @@ export default async function RootLayout({
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:site_name" content="کارخانه آجرپزی پاسارگاد" />
<meta name="author" content="پاسارگاد" />
<link rel="icon" href="/favicon.ico" />
{/* <link rel="icon" href="/favicon.ico" /> */}
<link
rel="icon"
type="image/png"
href="/favicon/favicon-96x96.png"
sizes="96x96"
/>
<link rel="icon" type="image/svg+xml" href="/favicon/favicon.svg" />
<link rel="shortcut icon" href="/favicon/favicon.ico" />
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicon/apple-touch-icon.png"
/>
<meta name="apple-mobile-web-app-title" content="Pasargad Bricks" />
<link rel="manifest" href="/favicon/site.webmanifest" />
</Head>
<body className={`min-h-svh w-full overflow-x-hidden ${fontClass}`}>
<NextIntlClientProvider locale={locale} messages={messages}>
+2 -1
View File
@@ -3,6 +3,7 @@ import routeFactory from '@/assets/constants/routeFactory';
import images from '@/assets/images/images';
import InnerPageBanner from '@/components/layout/innerPages/banner';
import SectionTitle from '@/components/layout/sectionTitle';
import ProjectsGrid from '@/components/pages/project/ProjectGrid';
import ProjectsGridView from '@/components/pages/project/projectGridView';
import Button from '@/components/uikit/button';
import { IPageParams } from '@/models/layout';
@@ -40,7 +41,7 @@ export default async function ProjectsPage({
<h3 className="text-xl tracking-normal max-lg:hidden">
{t('page_projects_description')}
</h3>
<ProjectsGridView
<ProjectsGrid
initialProjects={projects.data}
initialPagination={projects.pagination}
/>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

+46
View File
@@ -0,0 +1,46 @@
import { IPostThumbResponseData } from '@/app/api/blog/data';
import routeFactory from '@/assets/constants/routeFactory';
import Button from '@/components/uikit/button';
import { TLocales } from '@/models/layout';
import Image from 'next/image';
import Link from 'next/link';
import { useTranslations } from 'use-intl';
interface Props {
post: IPostThumbResponseData;
locale: TLocales;
}
const PostThumb = ({ post, locale }: Props) => {
const t = useTranslations();
const routes = routeFactory(t, locale);
return (
<div
key={post.id}
className="group flex flex-col gap-3 overflow-hidden bg-white md:gap-5"
>
<div className="relative aspect-[1.09] w-full overflow-hidden rounded-2xl">
<Image
src={post.imageSrc}
alt={post.title}
fill
className="h-auto w-full object-cover"
/>
<div className="invisible absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 backdrop-blur-xs transition-all group-hover:visible group-hover:opacity-100">
<Button link={routes.singlePost.route(post.id)} endIcon="showMore">
{t('page_blog_details_CTA')}
</Button>
</div>
</div>
<Link
href={routes.singlePost.route(post.id)}
className="group-hover:text-primary mb-4 text-center text-gray-500"
>
<h2 className="text-base font-medium">{post.title}</h2>
</Link>
</div>
);
};
export default PostThumb;
+3 -4
View File
@@ -2,11 +2,9 @@
import { useState } from 'react';
import Pagination from '@/components/layout/pagination';
import { getPosts } from '@/services/blog';
import { IPostThumbResponseData } from '@/app/api/blog/data';
import { IPagination } from '@/models/response';
import routeFactory from '@/assets/constants/routeFactory';
import { useParams } from 'next/navigation';
import { TLocales } from '@/models/layout';
import { useTranslations } from 'next-intl';
@@ -24,8 +22,6 @@ export default function PostsGrid({
const t = useTranslations();
const { locale } = useParams();
const routes = routeFactory(t, locale as TLocales);
const [posts, setPosts] = useState(initialPosts);
const [pagination, setPagination] = useState(initialPagination);
@@ -34,6 +30,9 @@ export default function PostsGrid({
.then((res) => {
setPosts(res.data);
setPagination(res.pagination);
document
.getElementById('postList')
?.scrollIntoView({ behavior: 'smooth', block: 'start' });
})
.catch((error) => {
console.error('Failed to fetch posts:', error);
+7 -34
View File
@@ -1,52 +1,25 @@
'use client';
import { IPostThumbResponseData } from '@/app/api/blog/data';
import routeFactory from '@/assets/constants/routeFactory';
import Button from '@/components/uikit/button';
import { TLocales } from '@/models/layout';
import { getTranslations } from 'next-intl/server';
import Image from 'next/image';
import Link from 'next/link';
import PostThumb from './PostThumb';
import { useTranslations } from 'next-intl';
export default async function PostsGridView({
export default function PostsGridView({
posts,
locale,
}: {
posts: IPostThumbResponseData[];
locale: TLocales;
}) {
const t = await getTranslations({ locale });
const t = useTranslations();
const routes = routeFactory(t, locale);
return (
<>
{posts.map((post) => (
<div
key={post.id}
className="group flex flex-col gap-3 overflow-hidden bg-white md:gap-5"
>
<div className="relative aspect-[1.09] w-full overflow-hidden rounded-2xl">
<Image
src={post.imageSrc}
alt={post.title}
fill
className="h-auto w-full object-cover"
/>
<div className="invisible absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 backdrop-blur-xs transition-all group-hover:visible group-hover:opacity-100">
<Button
link={routes.singlePost.route(post.id)}
endIcon="showMore"
>
{t('page_blog_details_CTA')}
</Button>
</div>
</div>
<Link
href={routes.singlePost.route(post.id)}
className="group-hover:text-primary mb-4 text-center text-gray-500"
>
<h2 className="text-base font-medium">{post.title}</h2>
</Link>
</div>
<PostThumb key={post.id} post={post} locale={locale} />
))}
</>
);
@@ -0,0 +1,41 @@
'use client';
import { Swiper, SwiperSlide } from 'swiper/react';
import { FreeMode } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/free-mode';
import { IPostThumbResponseData } from '@/app/api/blog/data';
import { useTranslations } from 'use-intl';
import routeFactory from '@/assets/constants/routeFactory';
import { TLocales } from '@/models/layout';
import PostThumb from './PostThumb';
interface Props {
posts: IPostThumbResponseData[];
locale: TLocales;
}
export default function PostsGridViewSlider({ posts, locale }: Props) {
const t = useTranslations();
const routes = routeFactory(t, locale);
return (
<div className="w-full">
<Swiper
modules={[FreeMode]}
freeMode
spaceBetween={16}
breakpoints={{
0: { slidesPerView: 1.1 },
768: { slidesPerView: posts.length, spaceBetween: 24 },
}}
className="w-full"
>
{posts.map((post) => (
<SwiperSlide key={post.id}>
<PostThumb post={post} locale={locale} />
</SwiperSlide>
))}
</Swiper>
</div>
);
}
+6 -3
View File
@@ -4,7 +4,7 @@ import { TLocales } from '@/models/layout';
import Button from '@/components/uikit/button';
import routeFactory from '@/assets/constants/routeFactory';
import { getPosts } from '@/services/blog';
import PostsGridView from '../blog/PostsGridView';
import PostsGridViewSlider from '../blog/postsGridViewSlider';
interface Props {
locale: TLocales;
className?: string;
@@ -38,8 +38,11 @@ export default async function HomePageBlog({ locale, className }: Props) {
</div>
</div>
<div className="mt-10 grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 lg:grid-cols-3 lg:gap-8">
<PostsGridView posts={posts.data?.slice(0, 3)} locale={locale} />
<div className="mt-10">
<PostsGridViewSlider
posts={posts.data?.slice(0, 3)}
locale={locale}
/>
</div>
<Button
link={routes.blog.route()}
+8 -1
View File
@@ -5,6 +5,8 @@ import { getProjects } from '@/services/projects';
import ProjectsGrid from '../project/ProjectGrid';
import Button from '@/components/uikit/button';
import routeFactory from '@/assets/constants/routeFactory';
import ProjectsGridView from '../project/projectGridView';
import ProjectsGridSlider from '../project/projectsGridSlider';
interface Props {
locale: TLocales;
}
@@ -36,8 +38,13 @@ export default async function HomePageProjects({ locale }: Props) {
</Button>
</div>
</div>
<div className="mt-10">
<ProjectsGridSlider
projects={projects.data?.slice(0, 3)}
locale={locale}
/>
</div>
<ProjectsGrid projects={projects.data?.slice(3)} className="mt-10" />
<Button
link={routes.projects.route()}
endIcon="showMore"
+37 -49
View File
@@ -1,61 +1,49 @@
'use client';
import Image from 'next/image';
import Link from 'next/link';
import { IProjectCardProps } from './project';
import Button from '@/components/uikit/button';
import { useTranslations } from 'next-intl';
import routeFactory from '@/assets/constants/routeFactory';
import { useParams } from 'next/navigation';
import { TLocales } from '@/models/layout';
import { useTranslations } from 'next-intl';
import ProjectsGridView from './projectGridView';
import Pagination from '@/components/layout/pagination';
import { IProjectThumbResponseData } from '@/app/api/projects/data';
import { IPagination } from '@/models/response';
import { useParams } from 'next/navigation';
import { useState } from 'react';
import { getProjects } from '@/services/projects';
export interface IProjectsGridProps {
initialProjects: IProjectThumbResponseData[];
initialPagination: IPagination;
}
export default function ProjectsGrid({
projects,
className = '',
}: {
projects: IProjectCardProps[];
className?: string;
}) {
initialProjects,
initialPagination,
}: IProjectsGridProps) {
const t = useTranslations();
const { locale } = useParams();
const routes = routeFactory(t, locale as TLocales);
const [projects, setProjects] = useState(initialProjects);
const [pagination, setPagination] = useState(initialPagination);
const fetchPage = (page: number) => {
getProjects(page)
.then((res) => {
setProjects(res.data);
setPagination(res.pagination);
document
.getElementById('projectList')
?.scrollIntoView({ behavior: 'smooth', block: 'start' });
})
.catch((error) => {
console.error('Failed to fetch Projects:', error);
});
};
return (
<div
className={`grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8 xl:grid-cols-3 xl:gap-10 ${className}`}
>
{projects.map((project, index) => (
<div
key={project.id}
className="group flex flex-col gap-3 overflow-hidden bg-white md:gap-5"
>
<div className="relative aspect-[1.4] w-full overflow-hidden rounded-2xl">
<Image
src={project.imageSrc}
alt={project.title}
fill
className="h-auto w-full object-cover"
/>
<div className="invisible absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 backdrop-blur-xs transition-all group-hover:visible group-hover:opacity-100">
<Button
endIcon="showMore"
link={routes.project.route(project.id)}
>
{t('com_home_projects_details_CTA')}
</Button>
</div>
</div>
<Link
href={routes.project.route(project.id)}
key={index}
className="group-hover:text-primary mb-4 text-center text-gray-500"
>
<h2 className="text-base font-medium">{project.title}</h2>
</Link>
</div>
))}
<div className="container mx-auto px-4 py-14 md:py-28">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
<ProjectsGridView projects={projects} locale={locale as TLocales} />
</div>
<Pagination {...pagination} onPageChange={fetchPage} />
</div>
);
}
@@ -0,0 +1,43 @@
import Image from 'next/image';
import Link from 'next/link';
import Button from '@/components/uikit/button';
import { useTranslations } from 'next-intl';
import routeFactory from '@/assets/constants/routeFactory';
import { IProjectThumbResponseData } from '@/app/api/projects/data';
import { TLocales } from '@/models/layout';
interface Props {
project: IProjectThumbResponseData;
locale: TLocales;
}
const ProjectThumb = ({ project, locale }: Props) => {
const t = useTranslations();
const routes = routeFactory(t, locale);
return (
<div className="group flex flex-col gap-3 overflow-hidden bg-white md:gap-5">
<div className="relative aspect-[1.4] w-full overflow-hidden rounded-2xl">
<Image
src={project.imageSrc}
alt={project.title}
fill
className="h-auto w-full object-cover"
/>
<div className="invisible absolute inset-0 flex items-center justify-center bg-black/20 opacity-0 backdrop-blur-xs transition-all group-hover:visible group-hover:opacity-100">
<Button endIcon="showMore" link={routes.project.route(project.id)}>
{t('com_home_projects_details_CTA')}
</Button>
</div>
</div>
<Link
href={routes.project.route(project.id)}
className="group-hover:text-primary mb-4 text-center text-gray-500"
>
<h2 className="text-base font-medium">{project.title}</h2>
</Link>
</div>
);
};
export default ProjectThumb;
@@ -1,37 +1,20 @@
'use client';
import { getProjects } from '@/services/projects';
import { IProjectGridViewProps } from './project';
import ProjectsGrid from './ProjectGrid';
import Pagination from '@/components/layout/pagination';
import { useState } from 'react';
import ProjectThumb from './ProjectThumb';
import { IProjectThumbResponseData } from '@/app/api/projects/data';
import { TLocales } from '@/models/layout';
export default function ProjectsGridView({
initialProjects,
initialPagination,
}: IProjectGridViewProps) {
const [projects, setProjects] = useState(initialProjects);
const [pagination, setPagination] = useState(initialPagination);
const fetchPage = (page: number) => {
getProjects(page)
.then((res) => {
setProjects(res.data);
setPagination(res.pagination);
document
.getElementById('projectList')
?.scrollIntoView({ behavior: 'smooth', block: 'start' });
})
.catch((error) => {
console.error('Failed to fetch Projects:', error);
});
};
interface Props {
projects: IProjectThumbResponseData[];
locale: TLocales;
}
export default function ProjectsGridView({ projects, locale }: Props) {
return (
<>
{projects?.length ? <ProjectsGrid projects={projects} /> : <></>}
<Pagination {...pagination} onPageChange={fetchPage} />
{projects?.map((project) => (
<ProjectThumb key={project.id} project={project} locale={locale} />
))}
</>
);
}
@@ -0,0 +1,40 @@
'use client';
import { Swiper, SwiperSlide } from 'swiper/react';
import { FreeMode } from 'swiper/modules';
import 'swiper/css';
import 'swiper/css/free-mode';
import ProjectThumb from './ProjectThumb';
import { TLocales } from '@/models/layout';
import { IProjectThumbResponseData } from '@/app/api/projects/data';
export default function ProjectsGridSlider({
projects,
locale,
className = '',
}: {
projects: IProjectThumbResponseData[];
locale: TLocales;
className?: string;
}) {
return (
<div className={className}>
<Swiper
modules={[FreeMode]}
freeMode
spaceBetween={16}
breakpoints={{
0: { slidesPerView: 1.1 },
768: { slidesPerView: projects.length, spaceBetween: 24 },
}}
className="w-full"
>
{projects.map((project) => (
<SwiperSlide key={project.id}>
<ProjectThumb project={project} locale={locale} />
</SwiperSlide>
))}
</Swiper>
</div>
);
}