This commit is contained in:
2025-01-17 19:17:39 +05:00
parent b0516c088e
commit f54368e10c
28 changed files with 221 additions and 191 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

@@ -1,12 +0,0 @@
<svg width="1464" height="512" viewBox="0 0 1464 512" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_4447_20946)">
<ellipse cx="804" cy="656" rx="356" ry="504" transform="rotate(90 804 656)" fill="#5545AC"/>
</g>
<defs>
<filter id="filter0_f_4447_20946" x="0" y="0" width="1608" height="1312" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_4447_20946"/>
</filter>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.
@@ -2,14 +2,14 @@ import { Metadata, ResolvingMetadata } from 'next';
export async function generateMetadata(
{},
_: ResolvingMetadata,
_: ResolvingMetadata
): Promise<Metadata> {
return {
title: 'Продукты - 360',
title: 'Продукты - Walk',
};
}
export default function SpheresLayout({
export default function WalkLayout({
children,
}: {
children: React.ReactNode;
@@ -1,5 +1,5 @@
import { InProcess } from '@/components/pages/InProcess';
export default function SpheresPage() {
export default function WalkPage() {
return <InProcess />;
}
-36
View File
@@ -1,36 +0,0 @@
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useCheckAuthQuery } from '@/queries/checkAuth';
import { useModalStore } from '@/stores/useModalStore';
import { Button } from '@/ui/Button';
import { PropsWithChildren, ReactNode } from 'react';
import { PlusIcon } from './icons/PlusIcon';
export function AddItemButton({
modal,
modalName,
children,
className,
color,
}: PropsWithChildren<{
modal: ReactNode;
modalName: string;
className?: string;
color: 'secondary' | 'primary';
}>) {
const { data: auth } = useCheckAuthQuery();
const { setModal } = useModalStore();
return (
auth && (
<Button
color={color}
className={className}
onClick={() => setModal(modal, modalName)}
icon={<ClassNameWrapper element={<PlusIcon />} className="w-4 h-4" />}
>
{children}
</Button>
)
);
}
+1
View File
@@ -181,6 +181,7 @@ export function Header() {
{productsOpened && (isMd || isLg) && (
<>
<motion.div
onClick={() => setProductsOpened(false)}
ref={productsRef}
animate={{
width: isLg
+1 -1
View File
@@ -14,7 +14,7 @@ export function Links({ text }: { text: string }) {
<div className="absolute bottom-0 left-[calc(77/460*100%)]">
<div className="relative lg:max-w-[calc(306/460*100%)] sm:max-w-[calc(186/273.33*100%)] max-w-[calc(270/340*100%)]">
<Image
className="!relative"
className="!relative w-full h-full"
src={'/img/components/products/stream.png'}
alt="stream"
fill
+5
View File
@@ -23,26 +23,31 @@ export function Products() {
))} */}
<ProductItem
key={1}
href={`/${products[0].title.toLowerCase()}`}
{...products[0]}
className={`md:aspect-[361.5/263] md:col-start-1 md:row-span-3`}
/>
<ProductItem
key={2}
href={`/${products[1].title.toLowerCase()}`}
{...products[1]}
className={`md:aspect-[361.5/263] md:col-start-1 md:row-span-3`}
/>
<ProductItem
key={3}
href={`/${products[2].title.toLowerCase()}`}
{...products[2]}
className={`max-md:aspect-[100/114] md:col-start-2 md:row-start-1 md:row-end-3`}
/>
<ProductItem
key={4}
href={`/${products[3].title.toLowerCase()}`}
{...products[3]}
className={`max-md:aspect-[100/114] md:col-start-2 md:row-start-3 md:row-end-5`}
/>
<ProductItem
key={5}
href={`/${products[4].title.toLowerCase()}`}
{...products[4]}
className={`col-span-2 md:col-span-1 md:col-start-2 md:row-start-5 md:row-end-7`}
/>
+1 -7
View File
@@ -1,12 +1,6 @@
export function PanDotsIcon() {
return (
<svg
width={24}
height={24}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 5C6 3.89543 6.89543 3 8 3C9.10457 3 10 3.89543 10 5C10 6.10457 9.10457 7 8 7C6.89543 7 6 6.10457 6 5Z"
fill="currentColor"
+1 -6
View File
@@ -1,11 +1,6 @@
export function PlusIcon() {
return (
<svg
width={40}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.0001 11.9998H18.0001M12.0001 11.9998L12.0001 6M12.0001 11.9998L12.0001 18M12.0001 11.9998H6"
stroke="white"
@@ -20,7 +20,7 @@ export function ArticleCard({
href={`/blog/${id}`}
className={`relative space-y-3${
className ? ' ' + className : ''
} hover:bg-[#7A7A7A] hover:backdrop-blur-[500]`}
} hover:backdrop-blur-[500px] hover:bg-[radial-gradient(ellipse_at_bottom,#7A7A7A,transparent)] bg-cover rounded-2xl relative`}
>
<Image
src={process.env.NEXT_PUBLIC_S3_BUCKET + cardImage}
@@ -12,17 +12,21 @@ export function ArticlesList({ tags }: { tags: string[] }) {
<TagsFilters type="article" tags={tags} />
<div className="mt-12">
{articles && articles.length > 0 ? (
<div className="col-start-2 flex flex-wrap gap-x-3 gap-y-6">
<div className="gap-x-3 gap-y-6 flex flex-wrap col-start-2">
{articles?.map((article, index) => (
<ArticleCard
key={article.id}
{...article}
className={index % 5 < 4 && index % 5 > 0 ? 'w-1/3' : 'w-1/2'}
className={
index % 5 < 4 && index % 5 > 0
? 'lg:w-1/3 sm:w-1/2'
: 'sm:w-1/2'
}
/>
))}
</div>
) : (
<p className="text-center heading1 font-medium">Статьи не найдены</p>
<p className="heading1 font-medium text-center">Статьи не найдены</p>
)}
</div>
</div>
@@ -1,6 +1,6 @@
'use client';
import { AddItemButton } from '@/components/AddItemButton';
import { AddItemWrapper } from '@/hocs/AddItemButton';
import { useCheckAuthQuery } from '@/queries/checkAuth';
import { Title } from '@/ui/Title';
@@ -14,14 +14,14 @@ export function ArticlesPageHeader() {
<span className="text-[#7A7A7A]"> новости, статьи и видео</span>
</Title>
{auth && (
<AddItemButton
<AddItemWrapper
color="primary"
modalName="addArticle"
className="sticky top-0"
modal={<></>}
>
Добавить статью
</AddItemButton>
</AddItemWrapper>
)}
</div>
);
+3 -16
View File
@@ -1,5 +1,5 @@
import { Title } from '@/ui/Title';
import Image from 'next/image';
import { VanyaBoom } from '@/ui/VanyaBoom';
import { Links } from '../Layout/Links';
export function InProcess() {
@@ -7,24 +7,11 @@ export function InProcess() {
<div className="lg:space-y-16 sm:space-y-12 space-y-10">
<div className="sm:max-lg:space-y-6 max-sm:space-y-8">
<Title headerLevel={1} className="col-span-full text-center">
Мы работаем{' '}
<Image
src={'/img/pages/about/vanya_workaet.png'}
alt="vanya workaet"
className="inline-block max-lg:hidden"
width={128}
height={62}
/>
Мы работаем <VanyaBoom className="inline-block max-lg:hidden" />
<br />
над этим разделом
</Title>
<Image
className="lg:hidden m-auto mt-6 mb-12"
src={'/img/pages/about/vanya_workaet.png'}
alt="vanya workaet"
width={128}
height={62}
/>
<VanyaBoom className="lg:hidden m-auto mt-6 mb-1" />
</div>
<Links text="А над этими мы уже поработали" />
</div>
@@ -1,12 +1,14 @@
'use client';
import { oldApi } from '@/api';
import { LogoIcon } from '@/components/icons/LogoIcon';
import regionsData from '@/consts/regionsData.json';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { GradientButton } from '@/ui/GradientButton';
import { Title } from '@/ui/Title';
import { AnimatePresence, motion } from 'framer-motion';
import Image from 'next/image';
import { useEffect, useState } from 'react';
import { CalculatorSwitch } from './CalculatorSwitch';
import { ConsultationRange } from './ConsultationsRange';
import { Region, RegionSelector } from './RegionSelector';
import { StatsColumn } from './StatColumn';
@@ -143,12 +145,20 @@ export function Calculator() {
)}
<ConsultationRange
consultations={consultations!}
setConsulatations={setConsultations}
/>
<CalculatorSwitch
enabled={calculated}
onToggle={() => setCalculated(!calculated)}
setConsultations={setConsultations}
/>
<GradientButton
onClick={() => setCalculated(!calculated)}
className="flex gap-x-3 items-center max-md:absolute top-0 max-md:-mt-7 left-[calc(50%-24px)]"
>
<ClassNameWrapper
element={<LogoIcon />}
className={
'lg:w-7 lg:h-7 w-5 h-5 ' +
(!calculated ? 'opacity-50' : 'opacity-100')
}
/>
</GradientButton>
</div>
<div className="space-y-10 lg:max-w-[1040px] w-full max-lg:order-1">
<div className="h-80 flex items-end justify-center w-full">
@@ -1,42 +0,0 @@
import { LogoIcon } from '@/components/icons/LogoIcon';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
export function CalculatorSwitch({
onToggle,
enabled,
}: {
onToggle: () => void;
enabled: boolean;
}) {
return (
<div className="flex gap-x-3 items-center max-md:absolute top-0 max-md:-mt-7 left-[calc(50%-24px)]">
<div
className={
'p-px rounded-full cursor-pointer transition-colors ' +
(enabled ? 'bg-gradient' : 'bg-[#37393B99]')
}
onClick={onToggle}
>
<div
className={
'rounded-full lg:border-[7px] border-[4px] border-black p-[14px] transition-colors ' +
(enabled ? 'bg-gradient' : 'bg-[#37393B99]')
}
>
<ClassNameWrapper
element={<LogoIcon />}
className={
'lg:w-7 lg:h-7 w-5 h-5 ' +
(!enabled ? 'opacity-50' : 'opacity-100')
}
/>
</div>
</div>
<p className="btnl font-medium text-[#7A7A7A] select-none max-md:hidden">
Инструмент
<br />
{enabled ? 'включен' : 'выключен'}
</p>
</div>
);
}
@@ -1,27 +1,55 @@
import { PanDotsIcon } from '@/components/icons/PanDotsIcon';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { DragEvent, useRef } from 'react';
import { MouseEvent, useRef, useState } from 'react';
export function ConsultationRange({
consultations,
setConsulatations,
setConsultations,
}: {
consultations: number;
setConsulatations: (consultations: number) => void;
setConsultations: (consultations: number) => void;
}) {
const root = useRef<HTMLDivElement>(null);
const barRef = useRef<HTMLDivElement>(null);
const panRef = useRef<HTMLDivElement>(null);
function handleDrag(e: DragEvent<HTMLDivElement>) {
const width = Math.max(
root.current!.clientWidth / 35,
Math.min(
e.clientX - panRef.current!.getBoundingClientRect().left,
root.current!.clientWidth
const [offset, setOffset] = useState(0);
const [start, setStart] = useState(0);
const [isMouseDown, setIsMouseDown] = useState(false);
function handleMouseDown(e: MouseEvent) {
console.log(e.clientX - root.current!.getBoundingClientRect().x);
setIsMouseDown(true);
setStart(
Math.max(
Math.min(
e.clientX - root.current!.getBoundingClientRect().x - offset,
root.current!.clientWidth
),
root.current!.clientWidth / 35
)
);
setConsulatations(Math.round((width / root.current!.clientWidth) * 350));
panRef.current!.style.width = `${width}px`;
}
function handleMouseMove(e: MouseEvent) {
const el = panRef.current;
if (!el || !isMouseDown) return;
const dx = Math.max(
Math.min(
e.clientX - root.current!.getBoundingClientRect().x - start,
root.current!.clientWidth - 48
),
(root.current!.clientWidth - 48) / 35
);
el.style.left = `${dx}px`;
setOffset(dx);
setConsultations(Math.round((dx / (root.current!.clientWidth - 48)) * 350));
barRef.current!.style.width = `${dx + 48}px`;
}
function handleMouseUp(e: MouseEvent) {
if (!isMouseDown) return;
setIsMouseDown(false);
}
return (
@@ -32,23 +60,20 @@ export function ConsultationRange({
ref={root}
>
<div
className="absolute left-0 top-0 rounded-2xl h-full bg-[#37393B99] backdrop-blur-2xl flex items-center pr-6 z-[2]"
ref={panRef}
ref={barRef}
className="absolute left-0 top-0 rounded-2xl h-full bg-[#37393B99] backdrop-blur-2xl flex items-center z-[2] pr-12"
>
<p className="btnl pl-8 font-medium select-none">{consultations}</p>
<div
className="self-center select-none absolute [user-drag:none] cursor-grab active:cursor-move [:.grabbing_*_&]:!cursor-grabbing focus:cursor-move"
onMouseDown={() => false}
style={{
left: Math.max((panRef.current?.clientWidth ?? 50) - 25, 0),
}}
draggable
onDrag={handleDrag}
onDragStart={() => false}
onDragEnd={handleDrag}
ref={panRef}
className="active:cursor-move right-6 absolute flex items-center self-center h-full pr-6 cursor-pointer select-none"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseUp}
onMouseUp={handleMouseUp}
>
<ClassNameWrapper
className="text-[#7A7A7A] select-none [user-drag:none] [-webkit-user-drag:none]"
className="text-[#7A7A7A] select-none w-6 h-6"
element={<PanDotsIcon />}
/>
</div>
+12 -5
View File
@@ -1,7 +1,12 @@
'use client';
import { PlusIcon } from '@/components/icons/PlusIcon';
import { ItemActions } from '@/components/ItemActions';
import { CompanyFormModal } from '@/components/modals/CompanyFormModal';
import { AddItemWrapper } from '@/hocs/AddItemButton';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useGetCompaniesQuery } from '@/queries/getCompanies';
import { GradientButton } from '@/ui/GradientButton';
import { Title } from '@/ui/Title';
import { getCompaniesCount } from '@/utils/getCompaniesCount';
import Image from 'next/image';
@@ -42,14 +47,16 @@ export function Clients() {
</div>
)
)}
{/* <AddItemButton
color="secondary"
<AddItemWrapper
modal={<CompanyFormModal action="create" />}
modalName="addCompany"
className="aspect-square flex justify-center items-center bg-[#232425] rounded-xl opacity-60 hover:opacity-100 transition-opacity p-5"
className="aspect-square flex flex-col items-center justify-center gap-3"
>
<PlusIcon />
</AddItemButton> */}
<GradientButton>
<ClassNameWrapper element={<PlusIcon />} className="w-7 h-7" />
</GradientButton>
<p className="btnl font-medium">Добавить</p>
</AddItemWrapper>
</div>
)}
</div>
+3 -16
View File
@@ -1,5 +1,5 @@
import { Title } from '@/ui/Title';
import Image from 'next/image';
import { VanyaBoom } from '@/ui/VanyaBoom';
import { Links } from '../Layout/Links';
export function NotFoundPage() {
@@ -7,24 +7,11 @@ export function NotFoundPage() {
<div className="lg:space-y-16 sm:space-y-12 space-y-10">
<div className="sm:max-lg:space-y-6 max-sm:space-y-8">
<Title headerLevel={1} className="col-span-full text-center">
Ошибка 404{' '}
<Image
src={'/img/pages/about/vanya_workaet.png'}
alt="vanya workaet"
className="inline-block max-lg:hidden"
width={128}
height={62}
/>
Ошибка 404 <VanyaBoom className="inline-block max-lg:hidden" />
<br />
такой страницы нет
</Title>
<Image
className="lg:hidden m-auto mt-6 mb-12"
src={'/img/pages/about/vanya_workaet.png'}
alt="vanya workaet"
width={128}
height={62}
/>
<VanyaBoom className="lg:hidden m-auto mt-6 mb-1" />
</div>
<Links text={'Зато есть много других интересных'} />
</div>
@@ -1,9 +1,12 @@
'use client';
import { AddItemButton } from '@/components/AddItemButton';
import { PlusIcon } from '@/components/icons/PlusIcon';
import { ProjectFormModal } from '@/components/modals/ProjectFormModal';
import { AddItemWrapper } from '@/hocs/AddItemButton';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useCheckAuthQuery } from '@/queries/checkAuth';
import { useGetProjectsCountQuery } from '@/queries/getProjectsCount';
import { Button } from '@/ui/Button';
import { Title } from '@/ui/Title';
import { getProjectsCount } from '@/utils/getProjectsCount';
@@ -13,21 +16,28 @@ export function ProjectsPageHeader() {
const auth = useCheckAuthQuery();
return (
<div className="lg:space-y-14 sm:space-y-8 space-y-10 relative">
<div className="lg:space-y-14 sm:space-y-8 relative space-y-10">
<Title className="text-center" headerLevel={2}>
За 15 лет работы мы реализовали
<br />
{getProjectsCount(count ?? 0)} для застройщиков
</Title>
{auth && (
<AddItemButton
color="primary"
<AddItemWrapper
modalName="addProject"
modal={<ProjectFormModal action={'create'} />}
className="sticky top-0 btns"
className="btns sticky top-0"
>
Добавить проект
</AddItemButton>
<Button
color="primary"
className="btns rounded-xl gap-2 py-2"
icon={
<ClassNameWrapper className="w-4 h-4" element={<PlusIcon />} />
}
>
Добавить проект
</Button>
</AddItemWrapper>
)}
</div>
);
+43
View File
@@ -0,0 +1,43 @@
import { useCheckAuthQuery } from '@/queries/checkAuth';
import { useModalStore } from '@/stores/useModalStore';
import { PropsWithChildren, ReactNode, useEffect, useRef } from 'react';
export function AddItemWrapper({
modal,
modalName,
children,
className,
}: PropsWithChildren<{
modal: ReactNode;
modalName: string;
className?: string;
}>) {
const { data: auth } = useCheckAuthQuery();
const { setModal } = useModalStore();
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
if (auth) {
ref.current?.children.item(0)!.addEventListener('click', () => {
console.log('asd');
setModal(modal, modalName);
});
return () => {
ref.current?.children.item(0)!.removeEventListener('click', () => {
setModal(modal, modalName);
});
};
}
}, [auth]);
return (
auth && (
<div ref={ref} className={className}>
{children}
</div>
)
);
}
+22
View File
@@ -0,0 +1,22 @@
import { PropsWithChildren } from 'react';
export function GradientButton({
children,
onClick,
className,
}: PropsWithChildren<{ onClick?: () => void; className?: string }>) {
return (
<button
onClick={onClick}
className={`bg-gradient-to-bl p-px rounded-full from-[#BE69F5] to-[#798FFF00]${
className ? ' ' + className : ''
}`}
>
<div className="p-2 bg-black rounded-full">
<div className="p-[14px] rounded-full bg-[#37393B99] active:bg-gradient-to-r from-[#6078F2] to-[#C868F5]">
{children}
</div>
</div>
</button>
);
}
+6 -3
View File
@@ -1,6 +1,7 @@
import { useMediaQueries } from '@/hooks/useMediaQueries';
import { motion } from 'framer-motion';
import Image from 'next/image';
import Link from 'next/link';
export interface IProduct {
id: number;
@@ -14,11 +15,13 @@ export function ProductItem({
title,
text,
className,
}: IProduct & { className?: string }) {
href,
}: IProduct & { className?: string; href: string }) {
const { isLg, isMd } = useMediaQueries();
return (
<div
<Link
href={href}
className={`md:p-5 p-2 flex flex-col justify-between relative max-md:items-start max-lg:bg-[#37393B99] rounded-2xl${
className ? ' ' + className : ''
}`}
@@ -78,6 +81,6 @@ export function ProductItem({
</div>
</motion.div>
<p className="opacity-60 btns font-medium md:hidden">{text}</p>
</div>
</Link>
);
}
+27
View File
@@ -0,0 +1,27 @@
import Image from 'next/image';
export function VanyaBoom({ className }: { className?: string }) {
return (
<div
className={`relative max-w-32 max-h-[78px]${
className ? ' ' + className : ''
}`}
>
<video
src="/videos/pages/inProcess/impla.mp4"
className="aspect-[128/62] absolute rounded-3xl"
muted
autoPlay
loop
playsInline
/>
<Image
src={'/img/pages/about/vanya_workaet.png'}
alt="vanya workaet"
className="relative mix-blend-lighten"
width={128}
height={62}
/>
</div>
);
}