This commit is contained in:
2024-10-15 14:45:00 +05:00
parent c2bf40866c
commit ef038029e7
30 changed files with 304 additions and 176 deletions
+1
View File
@@ -1,3 +1,4 @@
NEXT_PUBLIC_API=http://192.168.1.250:3001/
NEXT_PUBLIC_OLD_API=https://graff.estate/api
NEXT_PUBLIC_S3_BUCKET=https://storage.yandexcloud.net/dult-faib-knac-fint
NEXT_PUBLIC_TINYMCE_API_KEY=2vf68779upg45y46o6g5gaxldy9gzr399eyaaqa0ki3mj2h2
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 KiB

After

Width:  |  Height:  |  Size: 216 KiB

+5 -1
View File
@@ -1,6 +1,10 @@
import ky from 'ky';
export const oldApi = ky.extend({
prefixUrl: process.env.NEXT_PUBLIC_OLD_API,
});
export const api = ky.extend({
prefixUrl: process.env.NEXT_PUBLIC_API,
prefixUrl: process.env.NEXT_PUBLIC_OLD_API,
credentials: 'include',
});
+4 -2
View File
@@ -1,4 +1,5 @@
import { AvailablesSlider } from '@/components/pages/MainPage/Availables/AvailablesSlider';
import { Calculator } from '@/components/pages/MainPage/Calculator/Calculator';
import { Clients } from '@/components/pages/MainPage/Clients';
import { Datamining } from '@/components/pages/MainPage/Datamining';
import { IntegrationsSlider } from '@/components/pages/MainPage/Integrations/IntegrationsSlider';
@@ -6,6 +7,7 @@ import { Motivation } from '@/components/pages/MainPage/Motivation';
import { Projects } from '@/components/pages/MainPage/Projects';
import { Reviews } from '@/components/pages/MainPage/Reviews';
import { Showreel } from '@/components/pages/MainPage/Showreel';
import { Statistics } from '@/components/pages/MainPage/Statistics';
import { Streaming } from '@/components/pages/MainPage/Streaming';
import { Winners } from '@/components/pages/MainPage/Winners';
import { integrations } from '@/consts/integrations';
@@ -24,7 +26,7 @@ export default function HomePage() {
<DynamicEllipse />
<Motivation />
<Showreel />
{/* <Statistics /> */}
<Statistics />
<IntegrationsSlider integrations={integrations} />
<AvailablesSlider
availables={[
@@ -47,7 +49,7 @@ export default function HomePage() {
/>
<Streaming />
<Datamining />
{/* <Calculator /> */}
<Calculator />
<Reviews />
<Winners />
<Projects />
+1 -2
View File
@@ -20,8 +20,7 @@ export function Feedback() {
className="lg:px-6 px-4 sm:grid lg:grid-cols-12 sm:grid-cols-2 lg:gap-x-4 sm:gap-x-14 lg:gap-y-[68px] pb-20 pt-[70px]"
>
<h2 className="font-medium lg:col-span-7 sm:col-span-full h2 max-lg:mb-6">
Хотите использовать интерактивные тренажеры в обучении?
<br />
Хотите увеличить конверсию? <br />
<span className="text-gradient">Давайте обсудим детали.</span>
</h2>
<Button
+4 -14
View File
@@ -64,7 +64,10 @@ export function Header() {
</Button>
<button
ref={menuBtnRef}
className="h-full p-3 mr-0 aspect-square lg:hidden"
className={
'h-full p-3 mr-0 aspect-square lg:hidden' +
(menuOpen ? ' bg-[#798FFF]' : '')
}
onClick={() => setMenuOpen(prev => !prev)}
>
<ClassNameWrapper
@@ -89,22 +92,9 @@ export function Header() {
(menuOpen ? ' shadow-[0_0_0_9999px_rgba(0,0,0,.4)]' : '')
}
>
<ProductsList inBurger />
<BurgerLink href="/about">О нас</BurgerLink>
<BurgerLink href="/blog">Блог</BurgerLink>
<BurgerLink href="/projects">Проекты</BurgerLink>
{/* <div className="grid grid-cols-[2fr_1fr_1fr] sm:grid-cols-2">
<Button
onClick={() => {
setMenuOpen(false);
setModal(<ModalWithForm />, 'form');
}}
width="full"
className="font-semibold rounded-none sm:hidden btn-text"
>
Оставить заявку
</Button>
</div> */}
</motion.div>
)}
</header>
+2
View File
@@ -16,7 +16,9 @@ export function TagFilterItem({
multiple?: boolean;
}) {
const { push } = useRouter();
const pathname = usePathname();
const params = new URLSearchParams(useSearchParams());
function clickHandler() {
+15 -11
View File
@@ -1,20 +1,24 @@
export function PhoneIcon() {
return (
<svg
width="32"
height="32"
viewBox="0 0 32 32"
width={28}
height={28}
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
color="currentColor"
>
<g id="Icon/Phone" opacity="0.8">
<path
id="phone"
d="M13.2467 8.50638L10.4411 5.70883C9.93919 5.20838 9.12547 5.20839 8.62358 5.70883L6.29613 8.02957C3.39974 10.9176 7.6513 17.8 10.9553 21.0945C14.2395 24.3692 21.0664 28.5923 23.9628 25.7043L26.2902 23.3835C26.7921 22.8831 26.7921 22.0717 26.2902 21.5713L23.4846 18.7737C22.9827 18.2733 22.169 18.2733 21.6671 18.7737L19.5362 20.8984C19.4067 21.0276 19.2417 21.1047 19.0714 21.0372C18.6101 20.8542 17.4325 20.137 14.6538 17.4066C11.8643 14.6655 11.1485 13.4312 10.9713 12.9337C10.9044 12.7461 10.992 12.5669 11.1332 12.4261L13.2473 10.318C13.7492 9.8176 13.7486 9.00683 13.2467 8.50638Z"
fill="white"
/>
</g>
<path
d="M22.1325 24.6073L3.9834 6.45825V9.75808L18.8326 24.6073L22.1325 24.6073Z"
fill="white"
/>
<path
d="M10.5825 11.4078L13.8824 8.10802L9.75758 3.98323H6.45775L4.80783 5.63314L10.5825 11.4078Z"
fill="white"
/>
<path
d="M17.1809 18.0074L22.9556 23.7821L24.6055 22.1321L24.6055 18.8323L20.4807 14.7075L17.1809 18.0074Z"
fill="white"
/>
</svg>
);
}
+4 -6
View File
@@ -1,6 +1,6 @@
'use client';
import { api } from '@/api';
import { oldApi } from '@/api';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useModalStore } from '@/stores/useModalStore';
import { Button } from '@/ui/Button';
@@ -47,7 +47,7 @@ export function ModalWithForm() {
setIsLoading(true);
try {
await api
await oldApi
.post('mail', {
json: {
fullname: name,
@@ -89,9 +89,9 @@ export function ModalWithForm() {
);
return (
<div className="fixed flex flex-col gap-4 top-0 right-0 h-full sm:w-[408px] w-full bg-[#14161F] overflow-y-auto sm:p-8 p-6">
<div className="fixed flex flex-col gap-4 top-0 right-0 h-full sm:w-[408px] w-full bg-[#14161F] overflow-y-auto sm:p-8 p-6 z-40">
{!isSend ? (
<div className="space-y-8">
<div className="space-y-8 z-40 relative">
<div className="flex items-center justify-between">
<p className="font-medium accent">Оставьте заявку</p>
<button
@@ -113,7 +113,6 @@ export function ModalWithForm() {
Имя
</label>
<input
required
id="name"
type="text"
value={name}
@@ -137,7 +136,6 @@ export function ModalWithForm() {
/>
<div className="border-l border-[#3D425C]" />
<ReactInputMask
required
type="tel"
id={'tel'}
mask={placeholder?.replace(/\d/g, '9') ?? ''}
+15 -13
View File
@@ -4,7 +4,6 @@ import { AnimatePresence, motion } from 'framer-motion';
import Image from 'next/image';
import { RefObject, useEffect, useRef, useState } from 'react';
import { useOnClickOutside } from 'usehooks-ts';
import { CloseIcon } from '../icons/CloseIcon';
interface IProduct {
id: number;
@@ -51,7 +50,7 @@ export function ModalWithProducts({
exit={{
opacity: 0,
}}
className="pt-10 p-6 bg-[#14161F] bg-opacity-90 z-100 aspect-[1600/720] lg:top-16 top-12 relative space-y-6 backdrop-blur-3xl"
className="pt-10 p-6 bg-[#14161F] bg-opacity-90 z-100 lg:top-16 top-4 relative grid grid-cols-4 gap-y-4 backdrop-blur-3xl"
>
<motion.h4
initial={{ y: -76 }}
@@ -59,31 +58,34 @@ export function ModalWithProducts({
exit={{ y: -76 }}
key={'title'}
transition={{ duration: 0.8 }}
className="flex items-center justify-between font-medium h4"
className="font-medium h4 col-span-1"
>
GRAFF.estate
<button
onClick={() => setShow(false)}
className="hover:bg-[#3D425C] rounded-full p-2"
>
<CloseIcon />
</button>
Продукты
</motion.h4>
<motion.div
initial={{ y: -76 }}
animate={{ y: 0 }}
exit={{ y: -76 }}
key={'title'}
transition={{ duration: 0.8 }}
className="border-y border-[#3D425C] row-start-2"
/>
<motion.div
initial={{ y: -76 }}
animate={{ y: 0 }}
exit={{ y: -76 }}
key={'products'}
transition={{ duration: 0.8 }}
className="grid grid-rows-2 mt-px ml-px grid-col-3"
className="grid grid-rows-3 mt-px ml-px grid-cols-2 col-start-2 col-span-3 row-start-2"
>
{Products.map((product, index) => (
<ProductItem
key={product.id}
{...product}
className={`col-start-${(index % 3) + 1} row-start-${index < 3 ? 1 : 2}`}
className={`row-start-${(index % 3) + 1} col-start-${index < 3 ? 1 : 2}`}
/>
))}
<motion.div className="border-b border-r border-[#3D425C]" />
</motion.div>
</motion.div>
)}
@@ -105,7 +107,7 @@ function ProductItem({
}}
transition={{ duration: 0.2, type: 'just', delay: 0.1 }}
className={
'border border-[#3D425C] -mt-px -ml-px p-6 flex col-span-1 row-span-1 aspect-[517/304] bg-[url(/img/components/products/highlight.svg)] bg-[length:0px_0px] bg-left-top bg-no-repeat cursor-pointer ' +
'border border-[#3D425C] -mt-px -ml-px p-6 aspect-[588/207] flex bg-[url(/img/components/products/highlight.svg)] bg-[length:0px_0px] bg-left-top bg-no-repeat cursor-pointer ' +
className
}
>
@@ -1,6 +1,6 @@
'use client';
import { api } from '@/api';
import { oldApi } from '@/api';
import regionsData from '@/consts/regionsData.json';
import { useWindowWidth } from '@/hooks/useWindowWidth';
import { Descriptor } from '@/ui/Descriptor';
@@ -41,7 +41,7 @@ export function Calculator() {
const [calculated, setCalculated] = useState(true);
async function getRegionName() {
const result: any = await api.get('getRegionName').json();
const result: any = await oldApi.get('getRegionName').json();
if (result.error) {
setSelectedRegion(regionsData.find(region => region.id === 11));
@@ -55,6 +55,20 @@ export function Calculator() {
setSelectedRegion(foundRegion);
}
// useGetRegionNameQuery({
// onCompleted: data => {
// if (data.regionName.__typename === 'Region') {
// const { regionName } = data.regionName;
// setSelectedRegion(
// regionsData.find(region => region.name === regionName) ||
// regionsData.find(region => region.id === 11),
// );
// return;
// }
// setSelectedRegion(regionsData.find(region => region.id === 11));
// },
// });
useEffect(() => {
getRegionName();
}, []);
+41 -4
View File
@@ -1,5 +1,5 @@
import { ClientIcon } from '@/components/icons/ClientIcon';
import { ListIcon } from '@/components/icons/ListIcon';
import { MailIcon } from '@/components/icons/MailIcon';
import { PhoneIcon } from '@/components/icons/PhoneIcon';
import { Title } from '@/ui/Title';
import Image from 'next/image';
@@ -19,7 +19,30 @@ export function Datamining() {
</p>
</div>
<div className="p-6 my-4 w-full bg-[url(/img/pages/home/projectmanagment/Ellipse.png)] lg:flex flex-col justify-between sm:max-lg:col-start-1 col-span-1 space-y-[140px]">
<ListIcon />
{/* <ListIcon /> */}
<div className="flex max-w-16 relative">
<Image
src={'/img/components/datamining/2k.png'}
alt="room2"
width={64}
height={64}
className="!relative z-[2]"
/>
<Image
src={'/img/components/datamining/room1.png'}
alt="room1"
width={64}
height={64}
className="!relative -left-4 z-[1]"
/>
<Image
src={'/img/components/datamining/room2.png'}
alt="room1"
width={64}
height={64}
className="!relative -left-8"
/>
</div>
<div className="space-y-6">
<p className="font-medium sm:max-lg:h3 h4">
Актуальная информация о&nbsp;квартирах
@@ -31,7 +54,21 @@ export function Datamining() {
</div>
</div>
<div className="p-6 sm:my-4 max-sm:mb-4 w-full bg-[url(/img/pages/home/projectmanagment/Ellipse.png)] flex flex-col justify-between sm:max-lg:col-start-2 col-span-1 space-y-[140px]">
<ClientIcon />
<div className="flex max-h-16">
<Image
src={'/img/components/datamining/vova.png'}
alt="vova"
width={64}
height={64}
className="!relative z-[2]"
/>
<div className="p-[18px] rounded-full bg-[#33353E] max-h-[60px] relative -left-4 z-[1] drop-shadow-[0_4px_4px_rgba(0,0,0,0.25)]">
<MailIcon />
</div>
<div className="p-[18px] rounded-full bg-[#33353E] max-h-[60px] relative -left-8">
<PhoneIcon />
</div>
</div>
<div className="space-y-6">
<p className="font-medium sm:max-lg:h3 h4">
Создание карточки клиента
+10 -16
View File
@@ -4,7 +4,6 @@ import { ArrowMoreIcon } from '@/components/icons/ArrowMoreIcon';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useGetProjectsQuery } from '@/queries/projects/getProjects';
import { IProject } from '@/types/IProject';
import { Button } from '@/ui/Button';
import { Descriptor } from '@/ui/Descriptor';
import { getProjectsCount } from '@/utils/getProjectsCount';
import { getSortedProjects } from '@/utils/getSortedProjects';
@@ -48,9 +47,8 @@ export function Projects() {
<Descriptor title="Проекты" />
<p className="accent font-medium">
За <span className="text-gradient">13 лет</span> работы мы
реализовали
реализовали{' '}
<span className="text-gradient">
{' '}
{getProjectsCount(
Array.from(sortedProjects?.values() ?? [])?.flat().length,
)}
@@ -58,20 +56,16 @@ export function Projects() {
в&nbsp;разных городах России и&nbsp;мира
</p>
</div>
<Button
color="secondary"
className="lg:col-start-4 self-end w-full py-4 bg-[#14161F]"
icon={
<ClassNameWrapper
className="lg:w-8 lg:h-8 sm:w-6 sm:h-6"
element={<ArrowMoreIcon />}
/>
}
<Link
href={'/projects'}
className="lg:col-start-4 self-end w-full bg-[#14161F] btn-text flex justify-between items-center rounded-full border border-[#3D425C] py-5 px-6"
>
<Link href={'/projects'} className="btn-text">
Все проекты{' '}
</Link>
</Button>
Все проекты
<ClassNameWrapper
className="lg:w-8 lg:h-8 sm:w-6 sm:h-6"
element={<ArrowMoreIcon />}
/>
</Link>
</div>
<ProjectsSection
showMore={!all}
+1 -1
View File
@@ -20,7 +20,7 @@ export function Reviews() {
</Title>
<Descriptor title="отзывы клиентов" className="lg:hidden sm:mb-8 mb-6" />
<div className="relative w-full xl:aspect-[1552/616] sm:aspect-[720/412]">
<div className="lg:absolute flex flex-wrap bottom-10 left-10 gap-2 z-10 sm:max-lg:mb-6 max-sm:mb-4">
<div className="lg:absolute flex flex-wrap bottom-10 left-10 gap-2 z-[1] sm:max-lg:mb-6 max-sm:mb-4">
<ReviewTab
onClick={() => setTab(0)}
active={tab === 0}
+12 -16
View File
@@ -1,9 +1,10 @@
'use client';
import { api } from '@/api';
import { useGetProjectsQuery } from '@/queries/projects/getProjects';
import { IProject } from '@/types/IProject';
import { Descriptor } from '@/ui/Descriptor';
import { Title } from '@/ui/Title';
import { getProjectsCount } from '@/utils/getProjectsCount';
import { motion, useInView, useMotionValue, useSpring } from 'framer-motion';
import { Manrope } from 'next/font/google';
import Image from 'next/image';
@@ -15,20 +16,16 @@ const manrope = Manrope({ subsets: ['latin'] });
export function Statistics() {
const [projects, setProjects] = useState<IProject[]>([]);
async function getProjects() {
try {
const projects: IProject[] = await api.get('projects').json();
return projects;
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
useGetProjectsQuery({
variables: { devices: [] },
onCompleted(data) {
if (data.projects.__typename === 'Error') {
alert(data.projects.message);
} else if (data.projects.__typename === 'Projects') {
setProjects(data.projects.projects as IProject[]);
}
}
}
useEffect(() => {
getProjects().then(projects => setProjects(projects!));
}, []);
},
});
return (
<section className="lg:space-y-20 sm:space-y-12 space-y-8">
@@ -44,8 +41,7 @@ export function Statistics() {
<div className="grid lg:grid-cols-12 grid-cols-2 border-t border-[#3D425C]">
<div className="lg:col-span-3 col-span-2 lg:pt-10 sm:py-8 py-4 lg:border-r border-b border-[#3D425C] accent font-medium">
Мы собрали статистику за&nbsp;13&nbsp;лет работы c&nbsp;застройщиками,
реализовав
{/* {getProjectsCount(projects.length)} */}
реализовав {getProjectsCount(projects.length)}
</div>
<div className="lg:col-start-4 lg:col-span-full sm:col-span-2 lg:py-10 lg:pl-4 border-b border-[#3D425C] aspect-[1176/570] max-md:hidden">
<ProjectsMap />
+3 -3
View File
@@ -25,7 +25,7 @@ export function Streaming() {
выбора квартиры
</p>
</div>
<div className="lg:py-4 py-8 lg:pl-4 w-full lg:col-start-4 lg:col-span-full sm:col-span-2 lg:border-t sm:max-lg:border-b border-[#3D425C]">
<div className="lg:pt-4 py-8 lg:pl-4 w-full lg:col-start-4 lg:col-span-full sm:col-span-2 lg:border-t sm:max-lg:border-b border-[#3D425C]">
<video
src="/videos/pages/home/technology.mp4"
autoPlay
@@ -34,13 +34,13 @@ export function Streaming() {
className="lg:aspect-[1160/652]"
/>
</div>
<div className="lg:pt-10 sm:max-lg:py-8 max-sm:py-6 lg:flex flex-col justify-between lg:row-start-2 lg:col-span-3 sm:col-span-1 lg:border-y sm:max-lg:space-y-5 max-sm:space-y-3 max-sm:border-t lg:border-r border-[#3D425C]">
<div className="lg:pt-10 lg:pb-8 sm:max-lg:py-8 max-sm:py-6 lg:flex flex-col justify-between lg:row-start-2 lg:col-span-3 sm:col-span-1 lg:border-y sm:max-lg:space-y-5 max-sm:space-y-3 max-sm:border-t lg:border-r border-[#3D425C]">
<Descriptor title="Демоверсии" />
<p className="l-text">
Местоположение и устройство не имеют значения. Нужен только интернет
</p>
</div>
<div className="lg:p-4 sm:pb-8 min-[480px]:grid space-y-4 grid-cols-2 xl:grid-cols-3 gap-4 lg:col-start-4 col-span-full lg:border-y sm:max-lg:border-b border-[#3D425C]">
<div className="lg:p-4 sm:pb-8 min-[480px]:grid max-sm:space-y-4 grid-cols-2 xl:grid-cols-3 gap-4 lg:col-start-4 col-span-full lg:border-y sm:max-lg:border-b border-[#3D425C]">
<StreamingProject
city="Екатеринбург"
name="Re:volution Towers"
@@ -1,3 +1,4 @@
import { DeleteProjectModal } from '@/components/modals/DeleteProjectModal';
import {
IAddProjectFormInput,
ProjectFormModal,
@@ -8,6 +9,7 @@ import { useModalStore } from '@/stores/useModalStore';
import { IProject } from '@/types/IProject';
import { DeviceBadge } from '@/ui/DeviceBadge';
import { ProgressPie } from '@/ui/ProgressPie';
import { useApolloClient } from '@apollo/client';
import { motion } from 'framer-motion';
import { useSearchParams } from 'next/navigation';
@@ -19,12 +21,22 @@ export function ProjectCard({
stage = 6,
devices,
id,
releaseDate,
}: IProject) {
const { setModal } = useModalStore();
const client = useApolloClient();
const { data } = useCheckAuthQuery();
const [editProject] = useEditProjectMutation();
const [editProject] = useEditProjectMutation({
onCompleted() {
setModal(null, '');
client.refetchQueries({
include: ['GetProjects'],
});
},
});
const stagePercentage = Math.round((100 / 6) * stage);
const params = useSearchParams();
@@ -38,69 +50,76 @@ export function ProjectCard({
transition={{ duration: 1, ease: [0.58, 0.12, 0.27, 0.98], delay: 0.2 }}
className="group relative aspect-square p-4 flex items-end overflow-hidden"
>
<div className="absolute top-0 right-0 p-4 flex gap-2">
<button
onClick={() =>
setModal(
<ProjectFormModal
action="edit"
onSubmit={(data: IAddProjectFormInput) => {
console.log(data);
editProject({
variables: { id, input: data },
});
}}
defaultValues={{
...project,
devices: project.devices as Device[],
}}
/>,
'editProject',
)
}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Редактировать
</span>
</button>
<button
onClick={() => setModal(<DeleteProjectModal id={id} />, '')}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Удалить
</span>
</button>
</div>{' '}
{data?.checkAuth.__typename === 'CheckAuthResponse' &&
data.checkAuth.isAuth && (
<div className="absolute top-0 right-0 p-4 flex gap-2 z-10">
<button
onClick={() =>
setModal(
<ProjectFormModal
action="edit"
onSubmit={(data: IAddProjectFormInput) => {
editProject({
variables: { id, input: data },
});
}}
defaultValues={{
image,
name,
company,
city,
stage,
releaseDate,
devices,
}}
/>,
'editProject',
)
}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Редактировать
</span>
</button>
<button
onClick={() => setModal(<DeleteProjectModal id={id} />, '')}
className="group relative p-2 bg-black bg-opacity-60 hover:bg-opacity-70 transition-opacity rounded-full"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-6 h-6"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
/>
</svg>
<span className="pointer-events-none group-hover:opacity-100 opacity-0 transition-opacity absolute -bottom-[90%] left-[50%] -translate-x-[50%] bg-neutral-900 px-2 py-1 text-sm rounded-lg">
Удалить
</span>
</button>
</div>
)}
<div
className="group-hover:scale-110 transition-transform duration-500 absolute top-0 left-0 w-full h-full bg-cover bg-center bg-no-repeat"
style={{
@@ -1,10 +1,7 @@
'use client';
import { TagFilterItem } from '@/components/TagFilterItem';
import { YearFilterDropdown } from '@/components/YearFilterDropdown';
import { YearFilterItem } from '@/components/YearFilterItem';
import { ProjectTags } from '@/consts/ProjectTags';
import { ProjectYears } from '@/consts/ProjectYears';
import { Vertical } from '@/ui/Vertical';
import { useSearchParams } from 'next/navigation';
@@ -27,14 +24,14 @@ export function ProjectsFilters() {
/>
))}
</div>
<div className="flex flex-wrap gap-4 h-fit max-sm:hidden">
{/* <div className="flex flex-wrap gap-4 h-fit max-sm:hidden">
{ProjectYears.map(year => (
<YearFilterItem key={year} text={year} chosen={year === chosenYear} />
))}
<Vertical />
<YearFilterItem text="Все время" isAll chosen={!params.has('year')} />
</div>
<YearFilterDropdown years={ProjectYears} />
<YearFilterDropdown years={ProjectYears} /> */}
</div>
);
}
@@ -22,15 +22,16 @@ export function ProjectsSection({
const [filteredProjects, setFilteredProjects] = useState(projects);
useEffect(() => {
console.log(projects);
setFilteredProjects(
projects.filter(
project =>
(!params.has('tags') ||
(params.getAll('tags') as Device[]).every(tag =>
project.devices.includes(tag),
)) &&
(!params.has('year') ||
params.get('year') === project.releaseDate.split('-')[0]),
!params.has('tags') ||
(params.getAll('tags') as Device[]).every(tag =>
project.devices.includes(tag),
),
// &&(!params.has('year') ||
// params.get('year') === project.releaseDate.split('-')[0]),
),
);
}, [projects, params]);
+1 -1
View File
@@ -1,3 +1,3 @@
import { Device } from '@/types/IProject';
export const ProjectTags: Device[] = ['mobile', 'stream', 'touch', 'vr'];
export const ProjectTags: Device[] = ['Mobile', 'Stream', 'Touch', 'VR'];
+2 -8
View File
@@ -13,24 +13,18 @@
},
{
"id": 2,
"title": "View",
"text": "Интерактивное окно",
"image": "/img/components/products/view.png"
},
{
"id": 3,
"title": "Web",
"text": "Создание сайтов",
"image": "/img/components/products/web.png"
},
{
"id": 4,
"id": 3,
"title": "360",
"text": "Сферы для сайта",
"image": "/img/components/products/360.png"
},
{
"id": 5,
"id": 4,
"title": "Picture",
"text": "Архитектурная визуализация",
"image": "/img/components/products/picture.png"
+8
View File
@@ -186,6 +186,7 @@ export type Query = {
logout: LogoutResult;
project: ProjectResult;
projects: ProjectsResult;
regionName: RegionResult;
};
@@ -219,6 +220,13 @@ export type Quote = {
type: Scalars['String']['output'];
};
export type Region = {
__typename?: 'Region';
regionName: Scalars['String']['output'];
};
export type RegionResult = Error | Region;
export type Slider = {
__typename?: 'Slider';
images: Array<Image>;
@@ -0,0 +1,55 @@
import * as Types from '../../generated/graphql';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type GetRegionNameQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type GetRegionNameQuery = { __typename?: 'Query', regionName: { __typename?: 'Error', message: string } | { __typename?: 'Region', regionName: string } };
export const GetRegionNameDocument = gql`
query GetRegionName {
regionName {
... on Region {
regionName
}
... on Error {
message
}
}
}
`;
/**
* __useGetRegionNameQuery__
*
* To run a query within a React component, call `useGetRegionNameQuery` and pass it any options that fit your needs.
* When your component renders, `useGetRegionNameQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetRegionNameQuery({
* variables: {
* },
* });
*/
export function useGetRegionNameQuery(baseOptions?: Apollo.QueryHookOptions<GetRegionNameQuery, GetRegionNameQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetRegionNameQuery, GetRegionNameQueryVariables>(GetRegionNameDocument, options);
}
export function useGetRegionNameLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetRegionNameQuery, GetRegionNameQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetRegionNameQuery, GetRegionNameQueryVariables>(GetRegionNameDocument, options);
}
export function useGetRegionNameSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<GetRegionNameQuery, GetRegionNameQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<GetRegionNameQuery, GetRegionNameQueryVariables>(GetRegionNameDocument, options);
}
export type GetRegionNameQueryHookResult = ReturnType<typeof useGetRegionNameQuery>;
export type GetRegionNameLazyQueryHookResult = ReturnType<typeof useGetRegionNameLazyQuery>;
export type GetRegionNameSuspenseQueryHookResult = ReturnType<typeof useGetRegionNameSuspenseQuery>;
export type GetRegionNameQueryResult = Apollo.QueryResult<GetRegionNameQuery, GetRegionNameQueryVariables>;
+10
View File
@@ -0,0 +1,10 @@
query GetRegionName {
regionName {
... on Region {
regionName
}
... on Error {
message
}
}
}
+1
View File
@@ -0,0 +1 @@
export * from './getRegionName.generated';
+1 -1
View File
@@ -1,4 +1,4 @@
export type Device = 'stream' | 'touch' | 'mobile' | 'vr';
export type Device = 'Stream' | 'Touch' | 'Mobile' | 'VR';
export interface IProject {
id: number;