upd
This commit is contained in:
@@ -10,7 +10,9 @@ export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const { data } = useQuery({
|
||||
queryKey: ['checkAuth'],
|
||||
queryFn: async () => await api.get('auth/check').json<{ auth: boolean }>(),
|
||||
queryFn: async () => {
|
||||
return await api.get('auth/check').json<{ auth: boolean }>();
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -16,7 +16,6 @@ export default function LoginPage() {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutate: login } = useMutation({
|
||||
mutationKey: ['login'],
|
||||
mutationFn: async ({
|
||||
username,
|
||||
password,
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function MainLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<NewHeader />
|
||||
<main className="flex-1 pt-14 sm:px-5 px-4 overflow-hidden relative">
|
||||
<main className="flex-1 pt-14 sm:px-5 px-4 overflow-clip relative">
|
||||
{children}
|
||||
<Feedback />
|
||||
</main>
|
||||
|
||||
@@ -34,7 +34,7 @@ export default async function ProjectsPage({
|
||||
|
||||
return (
|
||||
<HydrationBoundary state={dehydrate(queryClient)}>
|
||||
<div className="lg:space-y-14 sm:space-y-8 space-y-4">
|
||||
<div className="lg:space-y-14 sm:space-y-8 space-y-4 relative">
|
||||
<ProjectsPageHeader />
|
||||
<ProjectsList tags={tags} city={city} companyId={companyId} />
|
||||
</div>
|
||||
|
||||
+42
-2
@@ -121,9 +121,9 @@ body {
|
||||
@apply 2xl:text-[clamp(20px,20px+(100vw-1560px)/360*8,28px)] text-[clamp(16px,16px+(100vw-360px)/1240*4,20px)] leading-[clamp(17.6px,17.6px+(100vw-360px)/1240*6.4,24px)];
|
||||
}
|
||||
|
||||
.accent {
|
||||
/* .accent {
|
||||
@apply -tracking-[.02em] md:text-[clamp(28px,28px+(100vw-768px)/832*4,32px)] text-[clamp(20px,20px+(100vw-360px)/408*8,28px)] md:leading-[clamp(28px,28px+(100vw-768px)/832*7.2,35.2px)] leading-[clamp(20px,20px+(100vw-360px)/408*8,28px)];
|
||||
}
|
||||
} */
|
||||
|
||||
.l-text {
|
||||
@apply 2xl:text-[clamp(20px,20px+(100vw-1560px)/360*4,24px)] text-[clamp(16px,16px+(100vw-360px)/1240*4,20px)] leading-[clamp(21.6px,21.6px+(100vw-360px)/1240*5.4,27px)];
|
||||
@@ -199,4 +199,44 @@ body {
|
||||
.card-gradient-5 {
|
||||
@apply bg-[radial-gradient(circle_closest-side_at_center,#5545AC,transparent)] bg-[length:0px_0px] hover:bg-[length:100%_100%] bg-center bg-no-repeat transition-all duration-300 delay-100;
|
||||
}
|
||||
/* */
|
||||
.line1 {
|
||||
@apply min-[1440px]:text-[clamp(96px,96px+(100vw-1440px)/480*32,128px)] md:text-[clamp(92px,92px+(100vw-768px)/672*8,100px)] sm:text-[clamp(40px,40px+(100vw-360px)/408*16,56px)] text-[40px] leading-[85%];
|
||||
}
|
||||
|
||||
.line2 {
|
||||
@apply min-[1440px]:text-[clamp(64px,64px+(100vw-1440px)/480*24,88px)] md:text-[clamp(40px,40px+(100vw-768px)/672*24,64px)] sm:text-[clamp(32px,32px+(100vw-360px)/408*8,40px)] text-[32px] leading-[95%];
|
||||
}
|
||||
|
||||
.heading1 {
|
||||
@apply min-[1440px]:text-[clamp(24px,24px+(100vw-1440px)/480*4,28px)] text-2xl leading-[1.167];
|
||||
}
|
||||
|
||||
.heading2 {
|
||||
@apply min-[1440px]:text-[clamp(20px,20px+(100vw-1440px)/480*4,24px)] sm:text-[clamp(16px,16px+(100vw-360px)/1080*4,20px)] text-base min-[1440px]:leading-[1.2] leading-[1.125];
|
||||
}
|
||||
|
||||
.accent {
|
||||
@apply min-[1440px]:text-[clamp(32px,32px+(100vw-1440px)/480*8,40px)] text-2xl min-[1440px]:leading-[1.1] leading-none;
|
||||
}
|
||||
|
||||
.text1 {
|
||||
@apply min-[1440px]:text-[clamp(14px,14px+(100vw-1440px)/480*4,18px)] text-sm leading-[1.35];
|
||||
}
|
||||
|
||||
.text2 {
|
||||
@apply min-[1440px]:text-[clamp(12px,12px+(100vw-1440px)/480*2,14px)] text-xs leading-[1.35];
|
||||
}
|
||||
|
||||
.btnl {
|
||||
@apply sm:text-[clamp(16px,16px+(100vw-360px)/1560*2,18px)] text-base leading-none;
|
||||
}
|
||||
|
||||
.btnm {
|
||||
@apply sm:text-[clamp(14px,14px+(100vw-360px)/1560*2,16px)] text-sm leading-none;
|
||||
}
|
||||
|
||||
.btns {
|
||||
@apply sm:text-[clamp(12px,12px+(100vw-360px)/1560*2,14px)] text-xs leading-none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export function CompanyActions() {
|
||||
return <div className="absolute top-0 right-0 p-4 flex gap-2 z-[5]"></div>;
|
||||
}
|
||||
@@ -14,13 +14,13 @@ import { SelectPhoneCode } from './SelectPhoneCode';
|
||||
export function Feedback() {
|
||||
return (
|
||||
<div id="contacts" className="pb-20 pt-[200px] flex gap-3">
|
||||
<h2 className="lg:col-span-7 sm:col-span-full h2 max-lg:mb-6 max-w-[50%]">
|
||||
<h2 className="lg:col-span-7 sm:col-span-full line2 font-medium max-lg:mb-6 max-w-[50%]">
|
||||
<span className="text-[#7A7A7A]">Хотите увеличить конверсию?</span>
|
||||
<br />
|
||||
Давайте обсудим детали.
|
||||
</h2>
|
||||
<div className="space-y-4 flex-1">
|
||||
<p className="font-medium text-xl">Нам нужна</p>
|
||||
<p className="font-medium heading2">Нам нужна</p>
|
||||
<FeedbackForm />
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,7 +84,7 @@ export function FeedbackForm() {
|
||||
type="text"
|
||||
placeholder="Имя*"
|
||||
{...register('name')}
|
||||
className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:m-text placeholder:font-medium placeholder:select-none"
|
||||
className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
@@ -100,7 +100,7 @@ export function FeedbackForm() {
|
||||
mask={placeholder?.replace(/\d/g, '9') ?? ''}
|
||||
maskChar={null}
|
||||
placeholder={placeholder}
|
||||
className="w-full transition-all bg-transparent rounded-none outline-none m-text placeholder:m-text placeholder:font-medium placeholder:select-none peer"
|
||||
className="w-full transition-all bg-transparent rounded-none outline-none placeholder:btnl placeholder:font-medium placeholder:select-none peer"
|
||||
/>
|
||||
<div className="bottom-0 absolute w-full border-b border-[#3D425C] peer-focus:border-white -mb-2" />
|
||||
</div>
|
||||
@@ -112,14 +112,14 @@ export function FeedbackForm() {
|
||||
type="text"
|
||||
placeholder="E-mail*"
|
||||
{...register('email')}
|
||||
className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:m-text placeholder:font-medium placeholder:select-none"
|
||||
className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:btnl placeholder:font-medium placeholder:select-none"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-3 items-stretch">
|
||||
<Button type="submit" rounded="2xl" className="py-5">
|
||||
<Button type="submit" rounded="2xl" className="py-5 btnl">
|
||||
Оставить заявку
|
||||
</Button>
|
||||
<Link href={'/policy'} className="text-xs">
|
||||
<Link href={'/policy'} className="text2 max-w-[30%]">
|
||||
<span className="text-[#7A7A7A]">
|
||||
*Нажимая кнопку отправить, вы принимаете
|
||||
</span>{' '}
|
||||
@@ -150,11 +150,11 @@ export function ProductOption({
|
||||
getValues('products').filter((product: string) => product !== text)
|
||||
);
|
||||
}
|
||||
}, [chosen]);
|
||||
}, [chosen, getValues, setValue, text]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`cursor-pointer px-6 py-[17px] transition-colors rounded-2xl ${
|
||||
className={`cursor-pointer px-6 py-[17px] transition-colors rounded-2xl btnm ${
|
||||
chosen ? 'bg-white text-black' : 'bg-[#37393B99]'
|
||||
}`}
|
||||
onClick={() => setChosen(!chosen)}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function ModalContainer() {
|
||||
modal && (
|
||||
<div
|
||||
className={
|
||||
'fixed left-0 z-[9] w-full h-full flex justify-center items-start transition-opacity' +
|
||||
'fixed left-0 z-[11] w-full h-full flex justify-center items-start transition-opacity' +
|
||||
(name === 'video' || name === 'form'
|
||||
? ' bg-black bg-opacity-90 [backdrop-filter:blur(10px);]'
|
||||
: '') +
|
||||
|
||||
@@ -14,8 +14,8 @@ export function NewFooter() {
|
||||
href={'tel:' + '8 800 770 00 67'.replaceAll(' ', '')}
|
||||
className="p-6 flex flex-col justify-between bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl w-1/2"
|
||||
>
|
||||
<p className="text-[#7A7A7A] text-sm font-medium">Позвонить</p>
|
||||
<div className="flex font-medium text-[64px] items-center">
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Позвонить</p>
|
||||
<div className="flex font-medium line2 items-center">
|
||||
8 800 770 00 67
|
||||
<ClassNameWrapper
|
||||
className="w-20 h-20"
|
||||
@@ -27,8 +27,8 @@ export function NewFooter() {
|
||||
href={'mailto:' + 'info@graff.tech'}
|
||||
className="p-6 flex flex-col justify-between bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl flex-1"
|
||||
>
|
||||
<p className="text-[#7A7A7A] text-sm font-medium">Написать</p>
|
||||
<div className="flex font-medium text-[64px] items-center">
|
||||
<p className="text-[#7A7A7A] text1 font-medium">Написать</p>
|
||||
<div className="flex font-medium line2 items-center">
|
||||
info@graff.tech
|
||||
<ClassNameWrapper
|
||||
className="w-20 h-20"
|
||||
@@ -66,16 +66,16 @@ export function NewFooter() {
|
||||
<div className="flex gap-x-6">
|
||||
<Link
|
||||
href={'/policy'}
|
||||
className="text-[#37393B] tex-sm font-medium leading-[18.9px]"
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px]"
|
||||
>
|
||||
Политика конфиденциальности и обработки персональных данных
|
||||
</Link>
|
||||
<p className="text-[#37393B] tex-sm font-medium leading-[18.9px]">
|
||||
<p className="text-[#37393B] text1 font-medium leading-[18.9px]">
|
||||
© 2024 GRAFF interactive. Все права защищены
|
||||
</p>
|
||||
<Link
|
||||
href={'https://graff.tech'}
|
||||
className="text-[#37393B] tex-sm font-medium leading-[18.9px]"
|
||||
className="text-[#37393B] text1 font-medium leading-[18.9px]"
|
||||
>
|
||||
graff.tech
|
||||
</Link>
|
||||
|
||||
@@ -41,13 +41,17 @@ export function NewHeader() {
|
||||
<HeaderLink href={'/projects'} text={'Проекты'} />
|
||||
<HeaderLink href={'/blog'} text={'Блог'} />
|
||||
<Button
|
||||
className="btnm font-medium"
|
||||
rounded="2xl"
|
||||
onClick={() => setModal(<ModalWithForm />, 'form')}
|
||||
>
|
||||
Оставить заявку
|
||||
</Button>
|
||||
{data?.auth && (
|
||||
<button className="p-3 rounded-full" onClick={() => logout()}>
|
||||
<button
|
||||
className="p-3 rounded-full btnm font-medium"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
Выйти
|
||||
</button>
|
||||
)}
|
||||
@@ -64,7 +68,7 @@ export function HeaderLink({ href, text }: { href: string; text: string }) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
className={`px-6 py-4 font-medium text-sm hover:bg-[#fff] hover:text-black rounded-2xl transition-colors ${
|
||||
className={`px-6 py-4 font-medium btnm hover:bg-[#fff] hover:text-black rounded-2xl transition-colors ${
|
||||
pathname === href && 'opacity-50'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -26,7 +26,7 @@ export function SelectPhoneCode({
|
||||
|
||||
function handleExpand(e: SyntheticEvent) {
|
||||
e.preventDefault();
|
||||
setOpen(prev => !prev);
|
||||
setOpen((prev) => !prev);
|
||||
}
|
||||
|
||||
function pickPhoneCode(phoneCode: string, country: CountryCode) {
|
||||
@@ -43,11 +43,11 @@ export function SelectPhoneCode({
|
||||
<Image
|
||||
width={16}
|
||||
height={8}
|
||||
src={countries.find(c => c.iso === currentCountry)?.flag || ''}
|
||||
src={countries.find((c) => c.iso === currentCountry)?.flag || ''}
|
||||
className="!relative w-4 sm:w-6"
|
||||
alt={currentCountry}
|
||||
/>
|
||||
<p className="m-text">{currentPhoneCode}</p>
|
||||
<p className="btnl font-medium">{currentPhoneCode}</p>
|
||||
<ClassNameWrapper
|
||||
className="flex-1 max-sm:w-4 sm:max-lg:w-5"
|
||||
element={open ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
@@ -56,10 +56,10 @@ export function SelectPhoneCode({
|
||||
{open && (
|
||||
<div className="space-y-1 absolute z-10 bg-[#14161F] top-[100%] -left-1 border border-t-0 rounded-b-lg border-[#3D425C] max-h-[300px] overflow-y-auto overflow-x-hidden">
|
||||
{getCountries()
|
||||
.map(country => [`+${getCountryCallingCode(country)}`, country])
|
||||
.map((country) => [`+${getCountryCallingCode(country)}`, country])
|
||||
.filter(
|
||||
([phonecode, country]) =>
|
||||
phonecode !== currentPhoneCode || country !== currentCountry,
|
||||
phonecode !== currentPhoneCode || country !== currentCountry
|
||||
)
|
||||
.map(([phoneCode, country]) => (
|
||||
<div
|
||||
@@ -68,7 +68,7 @@ export function SelectPhoneCode({
|
||||
onClick={() => pickPhoneCode(phoneCode, country as CountryCode)}
|
||||
>
|
||||
<Image
|
||||
src={countries.find(c => c.iso === country)?.flag || ''}
|
||||
src={countries.find((c) => c.iso === country)?.flag || ''}
|
||||
alt={country}
|
||||
className="!relative w-4 sm:w-6"
|
||||
width={16}
|
||||
|
||||
@@ -9,12 +9,18 @@ export function MediaUploader<T extends FieldValues>({
|
||||
fieldName,
|
||||
item,
|
||||
label,
|
||||
className,
|
||||
width = 300,
|
||||
height = 300,
|
||||
}: {
|
||||
dest: string;
|
||||
setValue: UseFormSetValue<T>;
|
||||
fieldName: Path<T>;
|
||||
item: Record<'img', string> & Record<'id', string>;
|
||||
label: string;
|
||||
className?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
}) {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [previewFile, setPreviewFile] = useState('');
|
||||
@@ -52,7 +58,12 @@ export function MediaUploader<T extends FieldValues>({
|
||||
}, [file, uploadFile]);
|
||||
|
||||
return (
|
||||
<label className="relative border border-dashed border-neutral-500 px-3 py-2 hover:bg-opacity-10 hover:bg-black cursor-pointer rounded-lg flex flex-col gap-2">
|
||||
<label
|
||||
className={
|
||||
'relative border border-dashed border-neutral-500 px-3 py-2 cursor-pointer rounded-lg flex flex-col gap-2 ' +
|
||||
className
|
||||
}
|
||||
>
|
||||
<input
|
||||
type="file"
|
||||
accept={'image/*'}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
'use client';
|
||||
|
||||
import { api } from '@/api';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { Button } from '@/ui/Button';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { CloseIcon } from '../icons/CloseIcon';
|
||||
import { MediaUploader } from '../MediaUploader';
|
||||
|
||||
interface IAddCompanyInput {
|
||||
title: string;
|
||||
color: string;
|
||||
mapIcon?: string;
|
||||
logo?: string;
|
||||
}
|
||||
|
||||
export function CompanyFormModal({}) {
|
||||
const { setModal } = useModalStore();
|
||||
|
||||
const { register, handleSubmit, setValue } = useForm<IAddCompanyInput>();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { mutateAsync } = useMutation<ICompany, Error, IAddCompanyInput>({
|
||||
mutationFn: async (json) => await api.post('companies', { json }).json(),
|
||||
onSuccess: async () =>
|
||||
await queryClient.invalidateQueries({ queryKey: ['companies'] }),
|
||||
});
|
||||
|
||||
const onSubmit = async (data: IAddCompanyInput) => {
|
||||
await mutateAsync(data);
|
||||
setModal(null, '');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="text-black bg-white p-4 rounded-lg relative top-[100px] space-y-4">
|
||||
<div className="flex justify-between items-center border-b border-[#ccc] pb-4 gap-4">
|
||||
<p className="text-xl">Добавление компании</p>
|
||||
<button
|
||||
onClick={() => setModal(null, '')}
|
||||
className="p-2 hover:bg-white hover:bg-opacity-10 transition-colors rounded-full"
|
||||
>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5 w-full">
|
||||
<div className="flex flex-col">
|
||||
<label htmlFor="title">Компания</label>
|
||||
<input {...register('title')} name="title" placeholder="Название" />
|
||||
</div>
|
||||
<div className="flex justify-between items-center gap-x-3 w-full">
|
||||
<MediaUploader
|
||||
className="bg-[#232425] aspect-square text-white flex-1"
|
||||
dest="projects"
|
||||
fieldName="logo"
|
||||
item={{ id: '', img: '' }}
|
||||
label="Логотип"
|
||||
setValue={setValue}
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
<MediaUploader
|
||||
className="aspect-square"
|
||||
dest="projects"
|
||||
fieldName="mapIcon"
|
||||
item={{ id: '', img: '' }}
|
||||
label="Иконка для карты"
|
||||
setValue={setValue}
|
||||
width={50}
|
||||
height={50}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<Button color="secondary" onClick={() => setModal(null, '')}>
|
||||
Отмена
|
||||
</Button>
|
||||
<Button type="submit" className="text-white">
|
||||
Добавить
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { api } from '@/api';
|
||||
import { PlusIcon } from '@/components/icons/PlusIcon';
|
||||
import { CompanyFormModal } from '@/components/modals/CompanyFormModal';
|
||||
import { useModalStore } from '@/stores/useModalStore';
|
||||
import { ICompany } from '@/types/ICompany';
|
||||
import { Title } from '@/ui/Title';
|
||||
@@ -55,7 +56,7 @@ export function Clients() {
|
||||
{checkAuth?.auth && (
|
||||
<button
|
||||
onClick={() => {
|
||||
// setModal(<div className="bg-white"></div>, 'addCompany');
|
||||
setModal(<CompanyFormModal />, 'addCompany');
|
||||
}}
|
||||
className="aspect-square flex justify-center items-center bg-[#232425] rounded-xl opacity-60 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
|
||||
@@ -80,7 +80,7 @@ export function InteractivePresentation() {
|
||||
className="rotate-[-4deg] right-2 relative"
|
||||
/>
|
||||
</div>
|
||||
<p className="font-medium text-sm">
|
||||
<p className="font-medium text1">
|
||||
Клиент всегда видит актуальные данные об интересующем его лоте,
|
||||
включая статус и стоимость.
|
||||
</p>
|
||||
@@ -104,7 +104,7 @@ export function InteractivePresentation() {
|
||||
<PhoneIcon />
|
||||
</div>
|
||||
</div>
|
||||
<p className="font-medium text-sm">
|
||||
<p className="font-medium text1">
|
||||
Клиент всегда видит актуальные данные об интересующем его лоте,
|
||||
включая статус и стоимость.
|
||||
</p>
|
||||
@@ -150,10 +150,10 @@ export function DoubleCard() {
|
||||
'Стоимость',
|
||||
'Инсоляция',
|
||||
'Особенности планировки',
|
||||
].map(tag => (
|
||||
].map((tag) => (
|
||||
<p
|
||||
key={tag}
|
||||
className="px-5 py-[11px] rounded-2xl leading-none border border-[#37393B] font-medium text-sm"
|
||||
className="px-5 py-[11px] rounded-2xl border border-[#37393B] font-medium btnm"
|
||||
>
|
||||
{tag}
|
||||
</p>
|
||||
@@ -176,13 +176,15 @@ export function CardContainer({
|
||||
}>) {
|
||||
return (
|
||||
<div
|
||||
className={`bg-[linear-gradient(to_top_right,#7A7A7A50,transparent),linear-gradient(to_bottom_left,#7A7A7A50,transparent)] p-6 rounded-lg flex flex-col ${className}${text ? '' : ' justify-between'}`}
|
||||
className={`bg-[linear-gradient(to_top_right,#7A7A7A50,transparent),linear-gradient(to_bottom_left,#7A7A7A50,transparent)] p-6 rounded-lg flex flex-col ${className}${
|
||||
text ? '' : ' justify-between'
|
||||
}`}
|
||||
>
|
||||
<p className="text-xl font-medium leading-5">{title}</p>
|
||||
<p className="heading2 font-medium">{title}</p>
|
||||
<div className={!!text ? 'flex-1 content-center m-auto' : ''}>
|
||||
{children}
|
||||
</div>
|
||||
{text && <p className="text-sm font-medium">{text}</p>}
|
||||
{text && <p className="text1 font-medium">{text}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ export function AwardItem({
|
||||
alt={title}
|
||||
/>
|
||||
<div className="space-y-2">
|
||||
<p className="font-medium text-2xl">{title}</p>
|
||||
<p className="font-medium text-sm text-[#7A7A7A]">{description}</p>
|
||||
<p className="font-medium heading1">{title}</p>
|
||||
<p className="font-medium text1 text-[#7A7A7A]">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -186,14 +186,16 @@ export function NewCalculator() {
|
||||
<div className="flex gap-x-3">
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden">
|
||||
<div className="space-y-11">
|
||||
<p className="font-medium text-sm">Срок реализации объекта</p>
|
||||
<p className="font-medium text-2xl">
|
||||
<span className="text-[64px]">
|
||||
<p className="font-medium text1">Срок реализации объекта</p>
|
||||
<p className="font-medium">
|
||||
<span className="line2">
|
||||
{calculated ? implementationPeriod : oldImplementationPeriod}
|
||||
</span>{' '}
|
||||
{calculated
|
||||
? implementationPeriodEnding
|
||||
: oldImplementationPeriodEnding}
|
||||
<span className="heading2">
|
||||
{calculated
|
||||
? implementationPeriodEnding
|
||||
: oldImplementationPeriodEnding}
|
||||
</span>
|
||||
</p>
|
||||
<AnimatePresence>
|
||||
{calculated && (
|
||||
@@ -205,7 +207,7 @@ export function NewCalculator() {
|
||||
y: 0,
|
||||
}}
|
||||
exit={{ opacity: 0, y: -10 }}
|
||||
className="rounded-2xl px-3 py-[7px] font-medium text-xs absolute bottom-7 right-6 bg-[#FF4517] z-[2]"
|
||||
className="rounded-2xl px-3 py-[7px] font-medium btns absolute bottom-7 right-6 bg-[#FF4517] z-[2]"
|
||||
>
|
||||
Продано
|
||||
</motion.p>
|
||||
@@ -236,12 +238,12 @@ export function NewCalculator() {
|
||||
</div>
|
||||
<div className="rounded-2xl bg-[linear-gradient(to_top,#7A7A7A40,#7A7A7A30)] p-7 w-1/2 relative overflow-hidden">
|
||||
<div className="space-y-11">
|
||||
<p className="font-medium text-sm">Ежемесячный доход</p>
|
||||
<p className="font-medium text-2xl">
|
||||
<span className="text-[64px]">
|
||||
<p className="font-medium text1">Ежемесячный доход</p>
|
||||
<p className="font-medium">
|
||||
<span className="line2">
|
||||
{calculated ? monthlyIncome : oldMonthlyIncome}
|
||||
</span>{' '}
|
||||
млн руб.
|
||||
<span className="heading2">млн руб.</span>
|
||||
</p>
|
||||
</div>
|
||||
<AnimatePresence>
|
||||
@@ -327,13 +329,13 @@ export function NewRegionSelector({
|
||||
|
||||
return (
|
||||
<div className="space-y-3 relative select-none">
|
||||
<p className="font-medium text-[#7A7A7A]">Регион</p>
|
||||
<p className="font-medium text-[#7A7A7A] btnl">Регион</p>
|
||||
<div
|
||||
ref={root}
|
||||
className="px-8 py-6 bg-[#37393B99] rounded-2xl flex items-center justify-between w-[360px]"
|
||||
onClick={() => setOpened(!opened)}
|
||||
>
|
||||
<p className="font-medium">{chosen.name}</p>
|
||||
<p className="font-medium btnl">{chosen.name}</p>
|
||||
{opened ? <ChevronUpIcon /> : <ChevronDownIcon />}
|
||||
</div>
|
||||
{opened && (
|
||||
@@ -381,7 +383,7 @@ export function ConsultationRange({
|
||||
|
||||
return (
|
||||
<div className="space-y-3 self-stretch">
|
||||
<p className="font-medium text-[#7A7A7A]">Консультаций в месяц</p>
|
||||
<p className="font-medium text-[#7A7A7A] btnl">Консультаций в месяц</p>
|
||||
<div
|
||||
className="px-7 py-9 bg-[#37393B99] rounded-2xl relative w-[360px] flex"
|
||||
ref={root}
|
||||
@@ -390,7 +392,7 @@ export function ConsultationRange({
|
||||
className="absolute left-0 top-0 rounded-2xl h-full bg-[#37393B99] backdrop-blur-2xl flex justify-between gap-x-3 items-center z-[2]"
|
||||
ref={panRef}
|
||||
>
|
||||
<p className="font-medium select-none pl-4">{consultations}</p>
|
||||
<p className="font-medium select-none pl-4 btnl">{consultations}</p>
|
||||
<div
|
||||
className="self-center select-none absolute [user-drag:none]"
|
||||
style={{
|
||||
@@ -406,7 +408,7 @@ export function ConsultationRange({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="absolute self-center right-8 font-medium text-[#7A7A7A] z-[1]">
|
||||
<p className="absolute self-center right-8 font-medium text-[#7A7A7A] z-[1] btnl">
|
||||
из 350
|
||||
</p>
|
||||
</div>
|
||||
@@ -428,8 +430,8 @@ export function StatsColumn({
|
||||
return (
|
||||
<div className="space-y-1 w-1/3">
|
||||
<div className="flex gap-1 items-center justify-center">
|
||||
<p className="font-medium text-[32px]">{value}</p>
|
||||
<p className="rounded-2xl px-2 p-[7px] bg-[#37393B99] text-xs font-medium">
|
||||
<p className="font-medium accent">{value}</p>
|
||||
<p className="rounded-2xl px-2 p-[7px] bg-[#37393B99] btns font-medium">
|
||||
{percents}%
|
||||
</p>
|
||||
</div>
|
||||
@@ -443,7 +445,7 @@ export function StatsColumn({
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-center font-medium text-sm">{title}</p>
|
||||
<p className="text-center font-medium text1">{title}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ export function NewMotivation() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
scrollY.on('change', value => setScrolled(value > 250));
|
||||
scrollY.on('change', (value) => setScrolled(value > 250));
|
||||
}, [scrollY]);
|
||||
|
||||
return (
|
||||
@@ -27,10 +27,16 @@ export function NewMotivation() {
|
||||
дороже
|
||||
</Title>
|
||||
<div
|
||||
className={`grid ${scrolled ? 'grid-cols-[3fr_1fr] w-[calc((100vw-48px)*1.33)]' : 'grid-cols-[1fr_1fr_1fr] w-[calc(100vw-48px)]'} grid-rows-2 gap-3 relative transition-all`}
|
||||
className={`grid ${
|
||||
scrolled
|
||||
? 'grid-cols-[3fr_1fr] w-[calc((100vw-48px)*1.33)]'
|
||||
: 'grid-cols-[1fr_1fr_1fr] w-[calc(100vw-48px)]'
|
||||
} grid-rows-2 gap-3 relative transition-all`}
|
||||
>
|
||||
<div
|
||||
className={`${scrolled ? 'col-span-1' : 'col-span-2'} row-span-2 relative`}
|
||||
className={`${
|
||||
scrolled ? 'col-span-1' : 'col-span-2'
|
||||
} row-span-2 relative`}
|
||||
>
|
||||
<video
|
||||
ref={ref}
|
||||
@@ -39,13 +45,17 @@ export function NewMotivation() {
|
||||
loop
|
||||
muted
|
||||
playsInline
|
||||
className={`rounded-xl object-cover h-full w-full transition-all ${scrolled ? 'aspect-[1400/734]' : ''}`}
|
||||
className={`rounded-xl object-cover h-full w-full transition-all ${
|
||||
scrolled ? 'aspect-[1400/734]' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`p-6 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl relative col-span-1 row-span-1 transition-all ${scrolled ? 'translate-x-full' : ''}`}
|
||||
className={`p-6 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl relative col-span-1 row-span-1 transition-all ${
|
||||
scrolled ? 'translate-x-full' : ''
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-xl max-w-[60%]">
|
||||
<p className="font-medium heading2 max-w-[60%]">
|
||||
Интеграция в офисы продаж
|
||||
</p>
|
||||
<div className="absolute bottom-6 right-6">
|
||||
@@ -58,9 +68,11 @@ export function NewMotivation() {
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`p-6 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl col-span-1 relative row-span-1 flex flex-col items-center justify-between space-y-7 transition-all ${scrolled ? 'translate-x-full' : ''}`}
|
||||
className={`p-6 bg-[linear-gradient(to_top_left,#7A7A7A50,transparent)] rounded-2xl col-span-1 relative row-span-1 flex flex-col items-center justify-between space-y-7 transition-all ${
|
||||
scrolled ? 'translate-x-full' : ''
|
||||
}`}
|
||||
>
|
||||
<p className="font-medium text-xl self-stretch">
|
||||
<p className="font-medium heading2 self-stretch">
|
||||
Удаленная демонстрация
|
||||
</p>
|
||||
<div className="">
|
||||
|
||||
@@ -49,7 +49,7 @@ export function NewProjects() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6 mt-40">
|
||||
<div className="space-y-16 mt-40">
|
||||
<div className="flex">
|
||||
<Title>
|
||||
За 15 лет работы мы реализовали{' '}
|
||||
|
||||
@@ -15,7 +15,15 @@ import { getCompaniesCount } from '@/utils/getCompaniesCount';
|
||||
import { getProjectsGroupedByCities } from '@/utils/getProjectsGroupedByCities';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Image from 'next/image';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Dispatch,
|
||||
SetStateAction,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useHover } from 'usehooks-ts';
|
||||
|
||||
export function NewStats() {
|
||||
const { data: projects } = useQuery({
|
||||
@@ -49,7 +57,7 @@ export function NewStats() {
|
||||
</Title>
|
||||
<div className="grid grid-cols-4 grid-rows-2 gap-3 aspect-[1400/462]">
|
||||
<div className="p-6 flex flex-col justify-between row-span-2 col-span-2 bg-[url(/img/pages/home/stats/building2.png),linear-gradient(to_bottom_right,#7A7A7A50,transparent)] bg-no-repeat bg-right-bottom bg-[length:65%,100%] rounded-lg">
|
||||
<p className="text-sm font-medium max-w-[35%]">
|
||||
<p className="text1 font-medium max-w-[35%]">
|
||||
Время реализации проекта сокращается до
|
||||
</p>
|
||||
<div className="font-medium">
|
||||
@@ -59,18 +67,16 @@ export function NewStats() {
|
||||
</div>
|
||||
<NewFigure
|
||||
percent={12}
|
||||
range={[30, 42]}
|
||||
text="Конверсия из бронирования в продажу увеличивается на"
|
||||
className=""
|
||||
/>
|
||||
<NewFigure
|
||||
percent={18}
|
||||
range={[30, 48]}
|
||||
text="Конверсия из консультации в бронирование увеличивается на"
|
||||
className="col-start-3"
|
||||
/>
|
||||
<div className="col-start-4 row-start-1 bg-[linear-gradient(to_top_left,#7a7a7a50,transparent)] p-6 rounded-lg flex flex-col justify-between relative">
|
||||
<p className="font-medium text-sm w-3/4">
|
||||
<p className="font-medium text1 w-3/4">
|
||||
Время на подготовку рекламных материалов сокращается до
|
||||
</p>
|
||||
<p className="font-medium text-[32px]">
|
||||
@@ -115,6 +121,22 @@ export function Map({
|
||||
projects: IProject[];
|
||||
chooseCity: (_: ICityProjects) => void;
|
||||
}) {
|
||||
const { cityPoint } = useCityPointStore();
|
||||
|
||||
const [currentHovered, setCurrentHovered] = useState<number | null>(null);
|
||||
|
||||
const groupedProjects = useMemo(
|
||||
() =>
|
||||
Array.from(
|
||||
getProjectsGroupedByCities(
|
||||
projects.filter(({ city }) =>
|
||||
cities.some(({ title }) => title === city)
|
||||
)
|
||||
).entries()
|
||||
),
|
||||
[projects]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Image
|
||||
@@ -123,15 +145,15 @@ export function Map({
|
||||
className="!relative aspect-[1027/560] object-cover"
|
||||
fill
|
||||
/>
|
||||
{Array.from(
|
||||
getProjectsGroupedByCities(
|
||||
projects.filter(({ city }) => city !== 'Dubai' && city !== 'Абу-Даби')
|
||||
).entries()
|
||||
).map(([city, projects]) => {
|
||||
{Array.from(groupedProjects).map(([city, projects], index) => {
|
||||
const point = cities.find(({ title }) => title === city)!;
|
||||
|
||||
return point ? (
|
||||
<CityPoint
|
||||
active={
|
||||
currentHovered === index ||
|
||||
(cityPoint.title === city && currentHovered === null)
|
||||
}
|
||||
key={city}
|
||||
chooseCity={chooseCity}
|
||||
title={city}
|
||||
@@ -142,7 +164,7 @@ export function Map({
|
||||
}
|
||||
x={point.x ?? 0}
|
||||
y={point.y ?? 0}
|
||||
extra={projects.length > 3 ? projects.length - 3 : 0}
|
||||
{...{ setCurrentHovered, index }}
|
||||
/>
|
||||
) : null;
|
||||
})}
|
||||
@@ -156,16 +178,30 @@ export function CityPoint({
|
||||
x,
|
||||
y,
|
||||
chooseCity,
|
||||
}: ICityProjects & { chooseCity: (_: ICityProjects) => void }) {
|
||||
active,
|
||||
setCurrentHovered,
|
||||
index,
|
||||
}: ICityProjects & {
|
||||
chooseCity: (_: ICityProjects) => void;
|
||||
active: boolean;
|
||||
setCurrentHovered: Dispatch<SetStateAction<number | null>>;
|
||||
index: number;
|
||||
}) {
|
||||
const companiesWithMapIcon = companies.filter((company) => company.mapIcon);
|
||||
|
||||
const { cityPoint } = useCityPointStore();
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
const hovered = useHover(ref);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentHovered(hovered ? index : null);
|
||||
}, [hovered, index, setCurrentHovered]);
|
||||
|
||||
return (
|
||||
<div className="absolute" style={{ top: y + '%', left: x + '%' }}>
|
||||
<div className="absolute" style={{ top: y + '%', left: x + '%' }} ref={ref}>
|
||||
<div
|
||||
className={`p-[11px] rounded-full bg-[#37393B99] hover:bg-transparent transition-colors relative z-10 peer ${
|
||||
cityPoint.title === title ? 'bg-transparent' : ''
|
||||
className={`p-[11px] rounded-full bg-[#37393B99] transition-colors relative z-10 ${
|
||||
active ? 'bg-transparent' : ''
|
||||
}`}
|
||||
onClick={() => chooseCity({ title, x, y, companies })}
|
||||
>
|
||||
@@ -173,14 +209,14 @@ export function CityPoint({
|
||||
</div>
|
||||
<div
|
||||
className={`transition-all duration-400 backdrop-blur-xl rounded-r-lg rounded-t-lg p-2 z-[20] min-w-[132px] ${
|
||||
cityPoint.title === title ? 'block' : 'hidden'
|
||||
} peer-hover:block bg-[#37393B99] absolute translate-x-5 -translate-y-full space-y-0.5`}
|
||||
active ? 'block' : 'hidden'
|
||||
} bg-[#37393B99] absolute translate-x-5 -translate-y-full space-y-0.5`}
|
||||
style={{
|
||||
left: x + '%',
|
||||
top: y + '%',
|
||||
}}
|
||||
>
|
||||
<p className="font-medium text-xs w-full leading-4">{title}</p>
|
||||
<p className="font-medium text2 w-full leading-4">{title}</p>
|
||||
<div className="flex py-px relative">
|
||||
{companiesWithMapIcon
|
||||
.slice(0, 3)
|
||||
@@ -199,6 +235,7 @@ export function CityPoint({
|
||||
src={process.env.NEXT_PUBLIC_S3_BUCKET + '' + mapIcon}
|
||||
alt={title}
|
||||
className="!relative object-cover"
|
||||
sizes=""
|
||||
fill
|
||||
/>
|
||||
)}
|
||||
@@ -259,7 +296,7 @@ export function ProjectsSlider({
|
||||
title: name,
|
||||
company,
|
||||
description,
|
||||
tags: devices,
|
||||
tags,
|
||||
image,
|
||||
stage = 1,
|
||||
}) => (
|
||||
@@ -277,19 +314,19 @@ export function ProjectsSlider({
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: company.color }}
|
||||
/>
|
||||
<p className="font-medium text-xs">{company.title}</p>
|
||||
<p className="font-medium tbns">{company.title}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="rounded-[17px] border border-[#37393B] text-xs flex items-center gap-x-1 px-2 py-1.5">
|
||||
<div className="rounded-[17px] border border-[#37393B] btns flex items-center gap-x-1 px-2 py-1.5">
|
||||
<ProgressPie value={Math.round((100 / 6) * stage)} />
|
||||
{Math.round((100 / 6) * stage)}%
|
||||
</div>
|
||||
{devices.map((device) => (
|
||||
{tags.map((tag) => (
|
||||
<div
|
||||
key={device}
|
||||
className="rounded-[17px] border border-[#37393B] px-2 py-1.5"
|
||||
key={tag}
|
||||
className="rounded-[17px] border border-[#37393B] px-2 py-1.5 btns"
|
||||
>
|
||||
{device}
|
||||
{tag}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -301,9 +338,10 @@ export function ProjectsSlider({
|
||||
fill
|
||||
className="!relative rounded object-cover aspect-[290/301]"
|
||||
alt={name}
|
||||
sizes=""
|
||||
/>
|
||||
</div>
|
||||
<p className="text-[#ffffff99]">{description}</p>
|
||||
<p className="text-[#ffffff99] text2">{description}</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@@ -311,7 +349,7 @@ export function ProjectsSlider({
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-[linear-gradient(to_left_top,#7A7A7A66,transparent)] rounded-2xl p-6 flex justify-between items-center relative bottom-0">
|
||||
<p className="text-[#ffffff99] font-medium text-sm">
|
||||
<p className="text-[#ffffff99] font-medium text1">
|
||||
<span className="text-white">{current}</span> из {count} в {city}
|
||||
</p>
|
||||
<div className="flex gap-x-1">
|
||||
|
||||
@@ -32,11 +32,11 @@ export function Streaming() {
|
||||
))}
|
||||
<div className="border border-[#3D425C] w-1/4 rounded-2xl flex justify-center items-center p-6">
|
||||
<div className="space-y-6 flex flex-col items-center">
|
||||
<p className="text-center font-medium text-xl">
|
||||
<p className="text-center font-medium heading2">
|
||||
Расскажем и покажем как это работает на созвоне
|
||||
</p>
|
||||
<Button
|
||||
className="rounded-lg"
|
||||
className="rounded-lg btnl px-6 py-4"
|
||||
onClick={() => setModal(<ModalWithForm />, 'form')}
|
||||
>
|
||||
Оставить заявку
|
||||
@@ -77,16 +77,18 @@ function StreamingProject({
|
||||
style={{ backgroundImage: `url(${image})` }}
|
||||
>
|
||||
<div className="space-y-3 font-medium">
|
||||
<p className="text-xl">{title}</p>
|
||||
<p className="heading1">{title}</p>
|
||||
<div className="flex">
|
||||
<div className="px-2 py-1.5 flex gap-1 items-center rounded-2xl bg-[#37393B99] text-xs">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: company.color }}
|
||||
/>
|
||||
{company.title}
|
||||
</div>
|
||||
<p className="px-3 py-[7px] bg-[#37393B99] rounded-2xl text-xs">
|
||||
{company && (
|
||||
<div className="px-2 py-1.5 flex gap-1 items-center rounded-2xl bg-[#37393B99] btns">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{ backgroundColor: company.color }}
|
||||
/>
|
||||
{company.title}
|
||||
</div>
|
||||
)}
|
||||
<p className="px-3 py-[7px] bg-[#37393B99] rounded-2xl btns">
|
||||
{city}
|
||||
</p>
|
||||
</div>
|
||||
@@ -95,7 +97,7 @@ function StreamingProject({
|
||||
href={href}
|
||||
className="hidden group-hover:block absolute w-full h-full left-0 bottom-0 rounded-2xl font-medium bg-[radial-gradient(#000000CC,transparent)] backdrop-blur-[3px] content-center text-center"
|
||||
>
|
||||
<p className="flex gap-2 justify-center">
|
||||
<p className="flex gap-2 justify-center btnl">
|
||||
Начать демонстрацию <ArrowMoreIcon />
|
||||
</p>
|
||||
</Link>
|
||||
|
||||
@@ -16,7 +16,9 @@ export function ProjectCard(project: IProject) {
|
||||
});
|
||||
|
||||
const stagePercentage = Math.round((100 / 6) * stage!);
|
||||
|
||||
const params = useSearchParams();
|
||||
|
||||
const chosenDevices = params.getAll('devices');
|
||||
|
||||
return (
|
||||
@@ -38,11 +40,11 @@ export function ProjectCard(project: IProject) {
|
||||
/>
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-gradient-card" />
|
||||
<div className="relative flex flex-col gap-4">
|
||||
<p className="lg:h4 h3 font-medium">{title}</p>
|
||||
<p className="heading1 font-medium">{title}</p>
|
||||
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{company && (
|
||||
<div className="px-2 py-1.5 font-medium rounded-2xl bg-[#37393B99] [backdrop-filter:blur(4px)] flex items-center gap-1">
|
||||
<div className="px-2 py-1.5 font-medium rounded-2xl bg-[#37393B99] [backdrop-filter:blur(4px)] flex items-center gap-1 btns">
|
||||
<div
|
||||
className="w-2 h-2 rounded-full"
|
||||
style={{
|
||||
@@ -53,7 +55,7 @@ export function ProjectCard(project: IProject) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="px-2 py-1.5 font-medium rounded-2xl bg-[#37393B99] [backdrop-filter:blur(4px)] flex items-center gap-1">
|
||||
<div className="px-2 py-1.5 font-medium rounded-2xl bg-[#37393B99] [backdrop-filter:blur(4px)] flex items-center gap-1 btns">
|
||||
{city}
|
||||
</div>
|
||||
|
||||
@@ -67,7 +69,7 @@ export function ProjectCard(project: IProject) {
|
||||
|
||||
{stage! < 6 && (
|
||||
<div className="bg-[#37393B99] px-3 py-2 rounded-full w-fit flex items-center gap-1 [backdrop-filter:blur(4px)]">
|
||||
<p className="l-caption font-semibold">{stagePercentage}%</p>
|
||||
<p className="btns font-semibold">{stagePercentage}%</p>
|
||||
<ProgressPie value={stagePercentage} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { api } from '@/api';
|
||||
import { projectsTags } from '@/consts/projectsTags';
|
||||
import { IProject } from '@/types/IProject';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
|
||||
@@ -28,24 +29,20 @@ export function ProjectsList({
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-[1fr_4fr_1fr]">
|
||||
<div className="space-y-2">
|
||||
{[
|
||||
'Все',
|
||||
'Интерактивные презентации',
|
||||
'Удаленная демонстрация',
|
||||
'Архитектурная визуализации',
|
||||
'Создание сайтов',
|
||||
'Тур по 360-сферам',
|
||||
].map((tag) => (
|
||||
<div className="grid grid-cols-[1fr_4fr_1fr] items-start gap-x-[34px] relative">
|
||||
<div className="space-y-2 sticky top-[50%] left-0 [align-self:start] row-span-full h-fit">
|
||||
{['Все', ...projectsTags].map((tag) => (
|
||||
<TagFilter
|
||||
key={tag}
|
||||
text={tag}
|
||||
active={(tags.includes(tag) && tag !== 'Все') || tags.length === 0}
|
||||
active={
|
||||
(tags.includes(tag) && tag !== 'Все') ||
|
||||
(tags.length === 0 && tag === 'Все')
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
<div className="col-start-2">
|
||||
{projects ? (
|
||||
<ProjectsSection projects={projects} />
|
||||
) : (
|
||||
@@ -65,9 +62,11 @@ function TagFilter({ text, active }: { text: string; active?: boolean }) {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`px-5 py-2 ${
|
||||
active ? 'bg-[#FFFFFF] text-black' : 'bg-[#37393B99] text-white'
|
||||
} rounded-xl w-fit`}
|
||||
className={`px-5 py-3 ${
|
||||
active
|
||||
? 'bg-[#FFFFFF] text-black'
|
||||
: 'bg-[#37393B99] text-white text-nowrap'
|
||||
} rounded-xl w-fit cursor-pointer font-medium btns`}
|
||||
onClick={() => {
|
||||
if (text === 'Все') params.delete('tags');
|
||||
else if (params.getAll('tags').includes(text))
|
||||
|
||||
@@ -43,8 +43,8 @@ export function ProjectsPageHeader() {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="lg:space-y-10 space-y-5">
|
||||
<Title className="text-center">
|
||||
<div className="lg:space-y-10 space-y-5 relative">
|
||||
<Title className="text-center" headerLevel={2}>
|
||||
За 15 лет работы мы реализовали
|
||||
<br />
|
||||
{getProjectsCount(count ?? 0)} для застройщиков
|
||||
|
||||
@@ -48,14 +48,20 @@ export function ProjectsSection({ projects }: { projects: IProject[] }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid xl:grid-cols-[repeat(4,calc(25vw-24px))] md:grid-cols-3 sm:grid-cols-2 gap-4 pt-8 border-b border-[#3D425C] py-10">
|
||||
<div
|
||||
className={`grid ${
|
||||
pathname === '/projects'
|
||||
? 'grid-cols-3'
|
||||
: 'xl:grid-cols-[repeat(4,calc(25vw-24px))]'
|
||||
} md:grid-cols-3 sm:grid-cols-2 gap-4`}
|
||||
>
|
||||
{projects.map((project) => (
|
||||
<ProjectCard key={project.id} {...project} />
|
||||
))}
|
||||
{pathname === '/' && (
|
||||
<Link
|
||||
href={'/projects'}
|
||||
className="rounded-xl bg-[#232425] aspect-square h-fit self-center p-5 opacity-60 hover:opacity-100 transition-opacity flex items-center justify-center text-base font-medium gap-2"
|
||||
className="rounded-xl bg-[#232425] aspect-square h-fit self-center p-5 opacity-60 hover:opacity-100 transition-opacity flex items-center justify-center btnl font-medium gap-2"
|
||||
>
|
||||
Смотреть все
|
||||
<ArrowRightIcon />
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
// import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import { useState } from 'react';
|
||||
import { getQueryClient } from './queryClient';
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ function makeQueryClient() {
|
||||
},
|
||||
dehydrate: {
|
||||
// include pending queries in dehydration
|
||||
shouldDehydrateQuery: query =>
|
||||
shouldDehydrateQuery: (query) =>
|
||||
defaultShouldDehydrateQuery(query) ||
|
||||
query.state.status === 'pending',
|
||||
},
|
||||
|
||||
@@ -5,5 +5,4 @@ export interface ICityProjects {
|
||||
y: number;
|
||||
title: string;
|
||||
companies: ICompany[];
|
||||
extra?: number;
|
||||
}
|
||||
|
||||
@@ -8,13 +8,11 @@ const manrope = Manrope({ subsets: ['latin'] });
|
||||
|
||||
export function NewFigure({
|
||||
percent,
|
||||
range: [from, to],
|
||||
text,
|
||||
className,
|
||||
}: {
|
||||
text: string;
|
||||
percent: number;
|
||||
range: [number, number];
|
||||
className?: string;
|
||||
}) {
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
@@ -47,9 +45,9 @@ export function NewFigure({
|
||||
className
|
||||
}
|
||||
>
|
||||
<p className="text-sm font-medium w-2/3">{text}</p>
|
||||
<p className="font-medium text-[32px]">
|
||||
<span className="text-8xl" ref={figureRef}>
|
||||
<p className="text1 font-medium w-2/3">{text}</p>
|
||||
<p className="font-medium accent">
|
||||
<span className="line1 font-medium" ref={figureRef}>
|
||||
{percent}
|
||||
</span>
|
||||
%
|
||||
|
||||
+13
-2
@@ -4,6 +4,17 @@ export function Title({
|
||||
className = '',
|
||||
headerLevel = 2,
|
||||
children,
|
||||
}: PropsWithChildren<{ className?: string; headerLevel?: 1 | 2 }>) {
|
||||
return <h1 className={`h${headerLevel} ` + className}>{children}</h1>;
|
||||
}: PropsWithChildren<{
|
||||
className?: string;
|
||||
headerLevel?: 1 | 2;
|
||||
}>) {
|
||||
return (
|
||||
<h1
|
||||
className={
|
||||
`font-medium ${headerLevel === 1 ? 'line1' : 'line2'} ` + className
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
}
|
||||
|
||||
+7
-3
@@ -6,6 +6,10 @@ const config: Config = {
|
||||
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||
theme: {
|
||||
extend: {
|
||||
// fontSize: {
|
||||
// line1: 'line1',
|
||||
// line2: 'line2',
|
||||
// },
|
||||
screens: {
|
||||
'desktop-figma': '1600px',
|
||||
},
|
||||
@@ -45,10 +49,10 @@ const config: Config = {
|
||||
const preflightStyles = postcss.parse(
|
||||
fs.readFileSync(
|
||||
require.resolve('tailwindcss/lib/css/preflight.css'),
|
||||
'utf8',
|
||||
),
|
||||
'utf8'
|
||||
)
|
||||
);
|
||||
preflightStyles.walkRules(rule => {
|
||||
preflightStyles.walkRules((rule) => {
|
||||
rule.selector = '.no-tailwind-base ' + rule.selector;
|
||||
});
|
||||
addBase(preflightStyles.nodes);
|
||||
|
||||
Reference in New Issue
Block a user