started setup to rendering fetched data
This commit is contained in:
@@ -1 +1 @@
|
||||
REVIEW_FORM_API=https://graff.estate/api
|
||||
NEXT_PUBLIC_API=https://graff.estate/api
|
||||
@@ -9,6 +9,7 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"date-fns": "^3.6.0",
|
||||
"framer-motion": "^11.3.9",
|
||||
"ky": "^1.4.0",
|
||||
"next": "14.2.5",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import ky from 'ky';
|
||||
|
||||
export const contactsFormApi = ky.extend({
|
||||
prefixUrl: process.env.REVIEW_FORM_API,
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import ky from 'ky';
|
||||
|
||||
export const api = ky.extend({
|
||||
prefixUrl: process.env.NEXT_PUBLIC_API,
|
||||
});
|
||||
+20
-51
@@ -1,29 +1,32 @@
|
||||
import PostCard from '@/components/Blog/PostCard';
|
||||
import { ArrowDownIcon } from '@/components/icons/ArrowDown';
|
||||
import { CategoryFilterItem } from '@/components/CategoryFilterItem';
|
||||
import { YearFilterItem } from '@/components/YearFilterItem';
|
||||
import { Posts } from '@/consts/Posts';
|
||||
import { Title } from '@/components/Title';
|
||||
|
||||
export default function BlogPage() {
|
||||
return (
|
||||
<div className="lg:pt-20 pt-14 lg:px-10 sm:px-6 px-4 lg:pb-6">
|
||||
<div className="sm:pb-5 pb-4 flex flex-col justify-between border-b border-[#3D425C]">
|
||||
<h1 className="font-medium h1 lg:mb-14 sm:mb-8 mb-4">Блог</h1>
|
||||
<div className="lg:pt-20 pt-14">
|
||||
<div className="sm:pb-5 pb-4 flex flex-col justify-between border-b lg:px-10 sm:px-6 px-4 border-[#3D425C]">
|
||||
<Title className="lg:mb-14 sm:mb-8 mb-4">Блог</Title>
|
||||
<div className="flex lg:items-center max-lg:flex-col gap-y-4 justify-between">
|
||||
<div className="flex lg:gap-3 sm:gap-2 gap-1 flex-wrap">
|
||||
<Category text="Все" count="99" chosen />
|
||||
<Category text="Недвижимость" count="19" />
|
||||
<Category text="Награды" count="19" />
|
||||
<Category text="Выстаки" count="19" />
|
||||
<div className="flex sm:gap-2 gap-1 flex-wrap">
|
||||
<CategoryFilterItem text="Все" count="99" chosen />
|
||||
<CategoryFilterItem text="Недвижимость" count="19" />
|
||||
<CategoryFilterItem text="Награды" count="19" />
|
||||
<CategoryFilterItem text="Выстаки" count="19" />
|
||||
</div>
|
||||
<div className="flex gap-x-4 h-fit max-sm:hidden">
|
||||
<YearItem text="2024" />
|
||||
<YearItem text="2023" />
|
||||
<YearItem text="2022" />
|
||||
<YearItem text="2021" />
|
||||
<YearItem text="2020" />
|
||||
<YearItem text="2019" />
|
||||
<YearItem text="2018" />
|
||||
<YearFilterItem text="2024" />
|
||||
<YearFilterItem text="2023" />
|
||||
<YearFilterItem text="2022" />
|
||||
<YearFilterItem text="2021" />
|
||||
<YearFilterItem text="2020" />
|
||||
<YearFilterItem text="2019" />
|
||||
<YearFilterItem text="2018" />
|
||||
<div className="w-px bg-[#737AA1]" />
|
||||
<YearItem text="Все время" />
|
||||
<YearFilterItem text="Все время" />
|
||||
</div>
|
||||
<div className="sm:hidden flex justify-between w-full border border-[#3D425C] px-6 py-4">
|
||||
Все время
|
||||
@@ -31,7 +34,7 @@ export default function BlogPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="lg:px-10 sm:px-6 px-4 lg:pb-6">
|
||||
{Posts.map(post => (
|
||||
<PostCard key={post.id} {...post} />
|
||||
))}
|
||||
@@ -39,37 +42,3 @@ export default function BlogPage() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Category({
|
||||
text,
|
||||
count,
|
||||
chosen = false,
|
||||
}: {
|
||||
text: string;
|
||||
count: string;
|
||||
chosen?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
'flex gap-x-1 btn-text font-semibold rounded-3xl px-3 sm:py-3 py-2 ' +
|
||||
(chosen ? 'bg-[#798FFF]' : 'border border-[#3D425C]')
|
||||
}
|
||||
>
|
||||
{text}
|
||||
<span
|
||||
className={
|
||||
'l-caption font-medium' + (chosen ? ' text-white' : ' text-[#737AA1]')
|
||||
}
|
||||
>
|
||||
({count})
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function YearItem({ text }: { text: string }) {
|
||||
return (
|
||||
<button className="text-[#737AA1] btn-text font-semibold">{text}</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
import { CategoryFilterItem } from '@/components/CategoryFilterItem';
|
||||
import { ProjectsList } from '@/components/Projects/ProjectsList';
|
||||
import { Title } from '@/components/Title';
|
||||
import { YearFilterItem } from '@/components/YearFilterItem';
|
||||
import { Vertical } from '@/ui/Vertical';
|
||||
|
||||
export default function ProjectsPage() {
|
||||
return <div>Проекты</div>;
|
||||
return (
|
||||
<div className="lg:pt-20 pt-14">
|
||||
<div className="sm:pb-5 pb-4 flex flex-col justify-between border-b lg:px-10 sm:px-6 px-4 border-[#3D425C]">
|
||||
<Title className="lg:mb-14 sm:mb-8 mb-4">Проекты</Title>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<CategoryFilterItem text="Все" chosen />
|
||||
<Vertical />
|
||||
<CategoryFilterItem text="Prime" />
|
||||
<CategoryFilterItem text="Web" />
|
||||
<CategoryFilterItem text="View" />
|
||||
</div>
|
||||
<div className="flex items-center lg:gap-x-4">
|
||||
<YearFilterItem text="2024" />
|
||||
<YearFilterItem text="2023" />
|
||||
<YearFilterItem text="2022" />
|
||||
<YearFilterItem text="2021" />
|
||||
<YearFilterItem text="2020" />
|
||||
<YearFilterItem text="2019" />
|
||||
<YearFilterItem text="2018" />
|
||||
<YearFilterItem text="2017" />
|
||||
<YearFilterItem text="2016" />
|
||||
<YearFilterItem text="2015" />
|
||||
<YearFilterItem text="2014" />
|
||||
<YearFilterItem text="2013" />
|
||||
<YearFilterItem text="2013" />
|
||||
<YearFilterItem text="2012" />
|
||||
<Vertical />
|
||||
<YearFilterItem text="Все время" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ProjectsList />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,12 +12,20 @@ export default function PostCard({
|
||||
id,
|
||||
}: IBlogPost) {
|
||||
const [date, month, year] = createdAt.split(' ');
|
||||
|
||||
return (
|
||||
<Link
|
||||
href={'/blog/' + id}
|
||||
className="border-[#3D425C] border-b py-6 flex justify-stretch gap-x-4"
|
||||
className="border-[#3D425C] border-b py-6 flex justify-stretch items-center gap-x-4 gap-y-3 max-sm:flex-col"
|
||||
>
|
||||
<div className="self-end flex-1 min-w-[105px] max-lg:hidden">
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
fill
|
||||
objectFit="cover"
|
||||
className="sm:hidden !static"
|
||||
/>
|
||||
<div className="self-end min-w-[105px] flex-1 max-lg:hidden">
|
||||
<h2 className="h2 font-medium mb-2">{date}</h2>
|
||||
<p className="descriptor font-medium">
|
||||
{month} {year}
|
||||
@@ -26,28 +34,28 @@ export default function PostCard({
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
width={496}
|
||||
height={388}
|
||||
className="max-lg:hidden object-cover"
|
||||
fill
|
||||
objectFit="cover"
|
||||
className="max-lg:hidden !static max-w-[496px]"
|
||||
/>
|
||||
<Image
|
||||
src={image}
|
||||
alt={title}
|
||||
width={264}
|
||||
height={240}
|
||||
className="hidden sm:max-lg:block object-cover"
|
||||
fill
|
||||
objectFit="cover"
|
||||
className="hidden sm:max-lg:block !static max-w-[264px] min-h-60"
|
||||
/>
|
||||
<div className="flex justify-between flex-col lg:max-w-[39vw] sm-max-lg:flex-1">
|
||||
<div className="sm:max-lg:mb-[66px]">
|
||||
<div className="flex justify-between flex-col lg:max-w-[39vw] sm-max-lg:flex-1 self-stretch max-sm:gap-y-3">
|
||||
<div className="">
|
||||
<div className="flex gap-2 mb-4">
|
||||
{tags.map(tag => (
|
||||
<PostTag text={tag} key={tag} />
|
||||
))}
|
||||
</div>
|
||||
<p className="accent font-medium mb-4">{title}</p>
|
||||
<p className="accent font-medium mb-5">{title}</p>
|
||||
<p className="m-text">{desc}</p>
|
||||
</div>
|
||||
<div className="flex sm:max-lg:justify-between lg:justify-end items-end">
|
||||
<div className="flex max-lg:justify-between lg:justify-end items-end max-sm:mt-2">
|
||||
<p className="descriptor lg:hidden font-medium opacity-70">
|
||||
{date} {month} {year}
|
||||
</p>
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function PostsFilters() {
|
||||
return <div>PostsFilters</div>;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Posts } from '@/consts/Posts';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function PostsList() {
|
||||
const [posts, setPosts] = useState(Posts);
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return <div>PostsList</div>;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
export function CategoryFilterItem({
|
||||
text,
|
||||
count,
|
||||
chosen = false,
|
||||
}: {
|
||||
text: string;
|
||||
count?: string;
|
||||
chosen?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
'flex gap-x-1 btn-text font-semibold rounded-3xl px-3 sm:py-3 py-2 ' +
|
||||
(chosen ? 'bg-[#798FFF]' : 'border border-[#3D425C]')
|
||||
}
|
||||
>
|
||||
{text}
|
||||
{count && (
|
||||
<span
|
||||
className={
|
||||
'l-caption font-medium' +
|
||||
(chosen ? ' text-white' : ' text-[#737AA1]')
|
||||
}
|
||||
>
|
||||
({count})
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import InputMask from 'react-input-mask';
|
||||
import { ChangeEvent, FormEvent, useState } from 'react';
|
||||
import { contactsFormApi } from '@/api/contactsFormApi';
|
||||
import { api } from '@/api';
|
||||
import { AsteriskIcon } from '../icons/AsteriskIcon';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { LoaderIcon } from '../icons/LoaderIcon';
|
||||
@@ -27,7 +27,7 @@ export function ContactsForm({ inModal = false }: { inModal?: boolean }) {
|
||||
setIsLoading(true);
|
||||
|
||||
try {
|
||||
await contactsFormApi
|
||||
await api
|
||||
.post('mail', {
|
||||
json: {
|
||||
fullname: name,
|
||||
|
||||
@@ -16,7 +16,8 @@ export function Feedback() {
|
||||
<br />с нами
|
||||
</h1>
|
||||
<p className="2xl:text-xl lg:text-lg font-semibold leading-tight">
|
||||
Хотите использовать интерактивные тренажеры в обучении? <br />
|
||||
Хотите увеличить конверсию?
|
||||
<br />
|
||||
Давайте обсудим детали!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,7 @@ export function Header() {
|
||||
<LogoWithTextIcon className="max-lg:hidden" />
|
||||
</Link>
|
||||
<div className="flex border-x border-[#3D425C] mx-auto max-xl:hidden">
|
||||
<div className="btn-text font-semibold flex gap-x-[135px] items-center px-10 py-[30px] hover:bg-[#3D425C] border-[#3D425C] border-r">
|
||||
<div className="btn-text font-semibold flex gap-x-[135px] items-center px-10 py-6 hover:bg-[#3D425C] border-[#3D425C] border-r">
|
||||
Продукты
|
||||
<ArrowDownIcon />
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import SkolkovoIcon from '../icons/SkolkovoIcon';
|
||||
import { SkolkovoIcon } from '../icons/SkolkovoIcon';
|
||||
import { Title } from '../Title';
|
||||
|
||||
export function Motivation() {
|
||||
return (
|
||||
<div className="lg:py-20 lg:px-6 flex justify-between">
|
||||
<div>
|
||||
<h1 className="h1 font-medium mb-10">
|
||||
<Title className="mb-10">
|
||||
Интерактивный инструмент
|
||||
<br />
|
||||
продаж
|
||||
<span className="text-gradient"> для застройщиков</span>
|
||||
</h1>
|
||||
</Title>
|
||||
<h3 className="h3 font-semibold">
|
||||
Помогаем девелоперам эффективно демонстрировать свой объект.
|
||||
<br />
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export function Video() {
|
||||
return <div>Video</div>;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Vertical } from '@/ui/Vertical';
|
||||
import { YearFilterItem } from '../YearFilterItem';
|
||||
import { CategoryFilterItem } from '../CategoryFilterItem';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function ProjectsFilters() {
|
||||
useEffect(() => {}, []);
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<CategoryFilterItem text="Все" chosen />
|
||||
<Vertical />
|
||||
<CategoryFilterItem text="Prime" />
|
||||
<CategoryFilterItem text="Web" />
|
||||
<CategoryFilterItem text="View" />
|
||||
</div>
|
||||
<div className="flex items-center lg:gap-x-4">
|
||||
<YearFilterItem text="2024" />
|
||||
<YearFilterItem text="2023" />
|
||||
<YearFilterItem text="2022" />
|
||||
<YearFilterItem text="2021" />
|
||||
<YearFilterItem text="2020" />
|
||||
<YearFilterItem text="2019" />
|
||||
<YearFilterItem text="2018" />
|
||||
<YearFilterItem text="2017" />
|
||||
<YearFilterItem text="2016" />
|
||||
<YearFilterItem text="2015" />
|
||||
<YearFilterItem text="2014" />
|
||||
<YearFilterItem text="2013" />
|
||||
<YearFilterItem text="2013" />
|
||||
<YearFilterItem text="2012" />
|
||||
<Vertical />
|
||||
<YearFilterItem text="Все время" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
'use client';
|
||||
|
||||
import { api } from '@/api';
|
||||
import { IProject } from '@/types/IProject';
|
||||
import { getSortedProjects } from '@/utils/getSortedProjects';
|
||||
import { getTime } from 'date-fns';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export function ProjectsList() {
|
||||
const [projects, setProjects] = useState<IProject[]>([]);
|
||||
const [projectLabels, setProjectLabels] = useState<(string | number)[]>([]);
|
||||
const [sortedProjects, setSortedProjects] =
|
||||
useState<Map<string | number, IProject[]>>();
|
||||
|
||||
async function getProjects() {
|
||||
try {
|
||||
const _projects: IProject[] = await api.get('projects').json();
|
||||
const _sortedProjects = getSortedProjects(_projects);
|
||||
const _projectLabels = Array.from(_sortedProjects.keys()).sort(
|
||||
(first, second) => getTime(second) - getTime(first),
|
||||
);
|
||||
|
||||
setProjects(_projects);
|
||||
setProjectLabels(_projectLabels);
|
||||
setSortedProjects(_sortedProjects);
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
alert(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getProjects();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('projects: ', projects);
|
||||
console.log('sortedProjects: ', sortedProjects);
|
||||
console.log('projectLabels: ', projectLabels);
|
||||
}, [sortedProjects, projectLabels, projects]);
|
||||
|
||||
return <div>ProjectsList</div>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
export function Title({
|
||||
className = '',
|
||||
children,
|
||||
}: PropsWithChildren<{ className?: string }>) {
|
||||
return <h1 className={'font-medium h1 ' + className}>{children}</h1>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export function YearFilterItem({ text }: { text: string }) {
|
||||
return (
|
||||
<button className="text-[#737AA1] btn-text font-semibold">{text}</button>
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function Skolkovo({
|
||||
export function SkolkovoIcon({
|
||||
className = '',
|
||||
type = 'standart',
|
||||
}: {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface IPostsTagsState {
|
||||
tags: string[];
|
||||
setTags: (tags: string[]) => void;
|
||||
}
|
||||
|
||||
export const usePostsTagsStore = create<IPostsTagsState>()(set => ({
|
||||
tags: [],
|
||||
setTags: tags => set({ tags }),
|
||||
}));
|
||||
@@ -0,0 +1,11 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface IPostsYearsState {
|
||||
years: string[];
|
||||
setYears: (years: string[]) => void;
|
||||
}
|
||||
|
||||
export const usePostsYearsStore = create<IPostsYearsState>()(set => ({
|
||||
years: [],
|
||||
setYears: years => set({ years }),
|
||||
}));
|
||||
@@ -0,0 +1,15 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface IProjectsFilters {
|
||||
devices: string[];
|
||||
years: string[];
|
||||
setDevices: (devices: string[]) => void;
|
||||
setYears: (years: string[]) => void;
|
||||
}
|
||||
|
||||
export const useProjectsFiltersStore = create<IProjectsFilters>()(set => ({
|
||||
devices: ['Все'],
|
||||
years: ['Все время'],
|
||||
setDevices: devices => set({ devices }),
|
||||
setYears: years => set({ years }),
|
||||
}));
|
||||
@@ -0,0 +1,12 @@
|
||||
type Device = 'stream' | 'touch' | 'mobile' | 'vr';
|
||||
|
||||
export interface IProject {
|
||||
id?: string;
|
||||
name: string;
|
||||
company: string;
|
||||
city: string;
|
||||
image: string;
|
||||
stage?: number;
|
||||
releaseDate: string;
|
||||
devices: Device[];
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export function Vertical() {
|
||||
return <div className="w-px h-5 bg-[#3D425C]" />;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// import { IProject } from '@/types/IProject';
|
||||
|
||||
// export function getProjectsDevices(projects: IProject[]) {
|
||||
// const projectsDevices: string[] = [];
|
||||
|
||||
// }
|
||||
@@ -0,0 +1,13 @@
|
||||
import { IBlogPost } from '@/types/IBlogPost';
|
||||
|
||||
export function getPostsTags(posts: IBlogPost[]) {
|
||||
const tags: string[] = [];
|
||||
|
||||
posts.forEach(post => {
|
||||
post.tags.forEach(tag => {
|
||||
if (!tags.includes(tag)) {
|
||||
tags.push(tag);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { getTime, getYear } from 'date-fns';
|
||||
import { IProject } from '../types/IProject';
|
||||
|
||||
export function getSortedProjects(projects: IProject[]) {
|
||||
const sortedProjects = new Map<string | number, IProject[]>();
|
||||
const sorted = projects.toSorted(
|
||||
(first, second) => getTime(second.releaseDate) - getTime(first.releaseDate),
|
||||
);
|
||||
|
||||
for (const project of sorted) {
|
||||
const key = project.stage !== 6 ? 'В работе' : getYear(project.releaseDate);
|
||||
sortedProjects.set(key, [...(sortedProjects.get(key) ?? []), project]);
|
||||
}
|
||||
|
||||
return sortedProjects;
|
||||
}
|
||||
@@ -686,6 +686,11 @@ data-view-byte-offset@^1.0.0:
|
||||
es-errors "^1.3.0"
|
||||
is-data-view "^1.0.1"
|
||||
|
||||
date-fns@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-3.6.0.tgz#f20ca4fe94f8b754951b24240676e8618c0206bf"
|
||||
integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==
|
||||
|
||||
debug@^3.2.7:
|
||||
version "3.2.7"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
|
||||
|
||||
Reference in New Issue
Block a user