update project api and page, set pagination in blog and create content generator component, create blog single page, ui fix
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import {
|
||||
IContentData,
|
||||
IContentDataLink,
|
||||
IContentImage,
|
||||
IDynamicContent,
|
||||
} from '@/models/dynamicContent';
|
||||
|
||||
function ImageContent(data: IContentImage) {
|
||||
return (
|
||||
<div className="flex flex-col items-center gap-3">
|
||||
<img
|
||||
src={data.src}
|
||||
alt={data.alt}
|
||||
className="mx-auto max-h-[60vh] max-w-[90vw] overflow-hidden rounded-2xl object-cover"
|
||||
/>
|
||||
{data.thumbTitle ? (
|
||||
<h5 className="text-center text-sm text-gray-400">{data.thumbTitle}</h5>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function titleContent(data: string) {
|
||||
return <h2 className="text-2xl font-bold text-gray-500">{data}</h2>;
|
||||
}
|
||||
|
||||
function fullContent(data: IContentData) {
|
||||
const splittedData = data.data?.split('\n') || [];
|
||||
return splittedData.map((e) => {
|
||||
return (
|
||||
<p className="indent-5 text-base text-gray-400">
|
||||
{generateLinkedContent(e, data.links)}
|
||||
</p>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function generateLinkedContent(data: string, links?: IContentDataLink[]) {
|
||||
const splitted = data.split(' ');
|
||||
|
||||
const mappedLinks = {} as Record<string, IContentDataLink>;
|
||||
links?.forEach((link) => {
|
||||
mappedLinks[link.key] = link;
|
||||
});
|
||||
|
||||
return (
|
||||
<p>
|
||||
{splitted.map((data) => {
|
||||
return mappedLinks[data] ? (
|
||||
<strong>
|
||||
<a
|
||||
href={mappedLinks[data].link}
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{mappedLinks[data].label}
|
||||
</a>{' '}
|
||||
</strong>
|
||||
) : (
|
||||
<>{data} </>
|
||||
);
|
||||
})}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
function listContent(data: string[], type: 'bullet' | 'numberList') {
|
||||
return (
|
||||
<ul>
|
||||
{data.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className={`list-inside ${type === 'bullet' ? 'list-disc' : 'list-decimal'} text-sm text-gray-400`}
|
||||
>
|
||||
<h4 className="inline text-sm">{item}</h4>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ContentGenerator({
|
||||
data,
|
||||
className,
|
||||
}: {
|
||||
data: IDynamicContent[];
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={`flex flex-col gap-3 md:gap-6 ${className}`}>
|
||||
{data.map((e) => {
|
||||
switch (e.type) {
|
||||
case 'title':
|
||||
return titleContent(e.data as string);
|
||||
case 'content':
|
||||
return <div>{fullContent(e.data as IContentData)}</div>;
|
||||
case 'image':
|
||||
return ImageContent(e.data as IContentImage);
|
||||
case 'bullet':
|
||||
case 'numberList':
|
||||
return listContent(e.data as string[], e.type);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ export default function Navbar() {
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<ul className="hidden space-x-6 text-sm md:flex lg:text-base">
|
||||
<ul className="hidden space-x-6 text-sm lg:flex lg:text-base">
|
||||
{links.map(({ route, title }) => (
|
||||
<li key={route()}>
|
||||
<Link
|
||||
@@ -71,7 +71,7 @@ export default function Navbar() {
|
||||
|
||||
{!open && (
|
||||
<button
|
||||
className="text-white md:hidden"
|
||||
className="text-white lg:hidden"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
<Icon name="menu" className="h-6 w-6" />
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { TLocales } from '@/models/layout';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
|
||||
export default async function AboutDescription({
|
||||
locale,
|
||||
className = '',
|
||||
}: {
|
||||
locale: TLocales;
|
||||
className?: string;
|
||||
}) {
|
||||
const t = await getTranslations({ locale });
|
||||
|
||||
return (
|
||||
<div className={`bg-primary w-full ${className}`}>
|
||||
<div className="container-fluid mx-auto py-20">
|
||||
<p className="text-xl text-white">{t('page_about_description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,22 +6,28 @@ import Pagination from '@/components/layout/pagination';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
import { getPosts } from '@/services/blog';
|
||||
import { IPostCardProps } from '@/app/api/blog/data';
|
||||
import { IPostData } from '@/app/api/blog/data';
|
||||
import { IPagination } from '@/models/response';
|
||||
import routeFactory from '@/assets/constants/routeFactory';
|
||||
import { useParams } from 'next/navigation';
|
||||
import { TLocales } from '@/models/layout';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import Button from '@/components/uikit/button';
|
||||
|
||||
export interface IPostsGridProps {
|
||||
initialPosts: IPostCardProps[];
|
||||
initialPagination: {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
totalItems: number;
|
||||
limit: number;
|
||||
};
|
||||
initialPosts: IPostData[];
|
||||
initialPagination: IPagination;
|
||||
}
|
||||
|
||||
export default function PostsGrid({
|
||||
initialPosts,
|
||||
initialPagination,
|
||||
}: IPostsGridProps) {
|
||||
const t = useTranslations();
|
||||
const { locale } = useParams();
|
||||
|
||||
const routes = routeFactory(t, locale as TLocales);
|
||||
|
||||
const [posts, setPosts] = useState(initialPosts);
|
||||
const [pagination, setPagination] = useState(initialPagination);
|
||||
|
||||
@@ -40,7 +46,10 @@ export default function PostsGrid({
|
||||
<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">
|
||||
{posts.map((post) => (
|
||||
<div key={post.id} className="overflow-hidden bg-white">
|
||||
<div
|
||||
key={post.id}
|
||||
className="group flex flex-col gap-4 overflow-hidden bg-white md:gap-6"
|
||||
>
|
||||
<div className="relative aspect-[1.09] w-full overflow-hidden rounded-4xl">
|
||||
<Image
|
||||
src={post.imageSrc}
|
||||
@@ -48,16 +57,22 @@ export default function PostsGrid({
|
||||
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>
|
||||
<div className="py-7">
|
||||
<h2 className="mb-5 text-xl font-semibold">{post.title}</h2>
|
||||
<a
|
||||
href={post.link}
|
||||
className="text-primary text-base hover:underline"
|
||||
>
|
||||
Read More →
|
||||
</a>
|
||||
</div>
|
||||
<Link
|
||||
href={routes.singlePost.route(post.id)}
|
||||
className="group-hover:text-primary text-center text-gray-500"
|
||||
>
|
||||
<h2 className="text-xl font-semibold">{post.title}</h2>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -4,10 +4,9 @@ import { useState } from 'react';
|
||||
import Button from '@/components/uikit/button';
|
||||
import FormField from '@/components/uikit/inputs';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import api from '@/lib/axios';
|
||||
import { setContact } from '@/services/contact';
|
||||
|
||||
const ContactForm = () => {
|
||||
const ContactForm = ({ className = '' }) => {
|
||||
const t = useTranslations();
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
@@ -57,7 +56,6 @@ const ContactForm = () => {
|
||||
};
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
console.log(e);
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
setContact(formData).finally(() => {
|
||||
@@ -68,7 +66,7 @@ const ContactForm = () => {
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="z-10 w-full rounded-4xl bg-white p-4 md:w-1/2"
|
||||
className={`z-10 w-full rounded-4xl bg-white ${className}`}
|
||||
>
|
||||
<h2 className="mt-8 mb-10 text-5xl text-gray-800">
|
||||
{t('com_contact_title')}{' '}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { TLocales } from '@/models/layout';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { useParams } from 'next/navigation';
|
||||
|
||||
const TouchUs = () => {
|
||||
const TouchUs = ({ className = '' }) => {
|
||||
const t = useTranslations();
|
||||
const params = useParams();
|
||||
const locale = params.locale as TLocales;
|
||||
|
||||
return (
|
||||
<div className="w-full p-8 md:w-1/2">
|
||||
<div className={`w-full ${className}`}>
|
||||
<SectionTitle
|
||||
title={t('pages_contact_title')}
|
||||
description={t('com_contact_touch_title')}
|
||||
|
||||
@@ -2,7 +2,6 @@ import SectionTitle from '@/components/layout/sectionTitle';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import { TLocales } from '@/models/layout';
|
||||
import { getProjects } from '@/services/projects';
|
||||
import { IProjectCardProps } from '../project/project';
|
||||
import ProjectsGrid from '../project/ProjectGrid';
|
||||
import Button from '@/components/uikit/button';
|
||||
import routeFactory from '@/assets/constants/routeFactory';
|
||||
@@ -12,14 +11,11 @@ interface Props {
|
||||
export default async function HomePageProjects({ locale }: Props) {
|
||||
const t = await getTranslations({ locale });
|
||||
const projects = await getProjects();
|
||||
console.log(projects);
|
||||
|
||||
const routes = routeFactory(t, locale);
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={`relative container mx-auto w-full bg-white bg-cover bg-fixed bg-center bg-no-repeat pt-28 max-lg:pt-6`}
|
||||
>
|
||||
<div className="relative container mx-auto w-full bg-white">
|
||||
<div className="relative z-20 grid grid-cols-1 lg:grid-cols-2 lg:gap-28">
|
||||
<SectionTitle
|
||||
title={t('pages_projects_title')}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
export interface IProductCardProps {
|
||||
imageSrc: string;
|
||||
title: string;
|
||||
id: string;
|
||||
model: string;
|
||||
color_code: string;
|
||||
size: string;
|
||||
categoryId: string;
|
||||
imageSrc: string;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ export default function ProjectsGrid({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`grid grid-cols-1 gap-8 md:grid-cols-2 md:gap-12 xl:grid-cols-3 xl:gap-18 ${className}`}
|
||||
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 className="group flex flex-col gap-4 overflow-hidden bg-white md:gap-6">
|
||||
<div className="relative aspect-[1.4] w-full overflow-hidden rounded-4xl">
|
||||
<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}
|
||||
@@ -38,9 +38,9 @@ export default function ProjectsGrid({
|
||||
<Link
|
||||
href={'/'}
|
||||
key={index}
|
||||
className="group-hover:text-primary text-center text-gray-500"
|
||||
className="group-hover:text-primary mb-4 text-center text-gray-500"
|
||||
>
|
||||
<h2 className="text-xl font-semibold">{project.title}</h2>
|
||||
<h2 className="text-base font-medium">{project.title}</h2>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -14,8 +14,6 @@ export default function ProjectsGridView({
|
||||
const [pagination, setPagination] = useState(initialPagination);
|
||||
|
||||
const fetchPage = (page: number) => {
|
||||
console.log(page);
|
||||
|
||||
getProjects(page)
|
||||
.then((res) => {
|
||||
setProjects(res.data);
|
||||
|
||||
+3
@@ -13,8 +13,11 @@ export interface IBaseButtonProps
|
||||
|
||||
export interface ITextButtonProps extends IBaseButtonProps {
|
||||
endIcon?: IconName;
|
||||
variant?: TButtonVariant;
|
||||
}
|
||||
export interface IIconButtonProps extends IBaseButtonProps {
|
||||
color?: 'primary' | 'secondary';
|
||||
size?: 'small' | 'medium' | 'large';
|
||||
}
|
||||
|
||||
export type TButtonVariant = 'text' | 'solid';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import React from 'react';
|
||||
import { IIconButtonProps, ITextButtonProps } from './button';
|
||||
import { IIconButtonProps, ITextButtonProps, TButtonVariant } from './button';
|
||||
import { Icon } from '../icons';
|
||||
import Link from 'next/link';
|
||||
|
||||
@@ -11,14 +11,20 @@ const Button: React.FC<ITextButtonProps> = ({
|
||||
endIcon,
|
||||
className,
|
||||
link,
|
||||
variant = 'solid',
|
||||
onClick,
|
||||
...props
|
||||
}) => {
|
||||
const wrapperClass = `group bg-primary relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''}`;
|
||||
const wrapperClass = `group relative h-12 cursor-pointer rounded-[0.5625rem] p-[0.125rem] flex w-fit ${className || ''} ${variant === 'solid' ? 'bg-primary' : ''}`;
|
||||
|
||||
return link ? (
|
||||
<Link href={link} className={wrapperClass} {...props}>
|
||||
<ButtonBody {...props} loading={loading} endIcon={endIcon}>
|
||||
<ButtonBody
|
||||
{...props}
|
||||
loading={loading}
|
||||
endIcon={endIcon}
|
||||
variant={variant}
|
||||
>
|
||||
{children}
|
||||
</ButtonBody>
|
||||
</Link>
|
||||
@@ -29,17 +35,28 @@ const Button: React.FC<ITextButtonProps> = ({
|
||||
onClick={onClick}
|
||||
className={wrapperClass}
|
||||
>
|
||||
<ButtonBody {...props} loading={loading} endIcon={endIcon}>
|
||||
<ButtonBody
|
||||
{...props}
|
||||
loading={loading}
|
||||
endIcon={endIcon}
|
||||
variant={variant}
|
||||
>
|
||||
{children}
|
||||
</ButtonBody>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const variantClassNames = (variant: TButtonVariant) =>
|
||||
variant === 'solid'
|
||||
? 'flex items-center justify-center rounded-lg bg-white text-gray-500'
|
||||
: 'text-primary text-base hover:underline';
|
||||
|
||||
const ButtonBody: React.FC<ITextButtonProps> = ({
|
||||
children,
|
||||
loading,
|
||||
endIcon,
|
||||
variant = 'solid',
|
||||
}) => (
|
||||
<div className="flex">
|
||||
{loading ? (
|
||||
@@ -65,11 +82,7 @@ const ButtonBody: React.FC<ITextButtonProps> = ({
|
||||
</svg>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-lg bg-white px-3 text-gray-500`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
<div className={`px-3 ${variantClassNames(variant)}`}>{children}</div>
|
||||
|
||||
{endIcon && !loading ? (
|
||||
<span className="flex items-center justify-center p-3">
|
||||
|
||||
Reference in New Issue
Block a user