refactor: enhance localization strings and standardize component formatting across multiple files

This commit is contained in:
2025-07-11 16:12:53 +05:00
parent 3b7dd8cd01
commit c54272780f
8 changed files with 146 additions and 140 deletions
+4 -4
View File
@@ -13,9 +13,9 @@
"develope": "We help developers sell",
"easier": "real estate easier and ",
"faster": "faster",
"cheaper": "cheaper",
"expensive": "more expensive ",
"integration": "Integration in sales offices",
"remote_demo": "Remote demo"
"remote_demo": "Remote demonstrations"
},
"integrations": {
"title": "Integration in sales offices",
@@ -33,7 +33,7 @@
},
"interactive_presentation": {
"title": "Interactive presentation",
"experience": "enhances property selection",
"experience": "enhances property selection experience",
"increase": "and boosts apartment sales in a residential complex"
},
"search_and_select": {
@@ -50,7 +50,7 @@
"three_d_tour": {
"title": "Virtual tour of a residential complex",
"court": "Courtyard",
"mop": "CUA",
"mop": "Common areas",
"flats": "Apartments",
"parking": "Parking",
"office": "Commercial"
+2 -2
View File
@@ -223,7 +223,7 @@ export function Header() {
)}
</AnimatePresence>
</div>
{pathname.startsWith("/projects") && (
{/* {pathname.startsWith("/projects") && (
<Link
href={"https://dprofile.ru/graff.estate"}
className="max-xl:hidden rounded-[20px] bg-[#37393B99] backdrop-blur-[20px] hover:bg-[#232425] py-5 pl-[63px] pr-[33px] right-5 fixed z-[2] top-5 overflow-clip bg-[url(/img/components/header/dp.png)] bg-no-repeat bg-right-bottom"
@@ -237,7 +237,7 @@ export function Header() {
/>
<p className="btnm font-medium">{t("case")}</p>
</Link>
)}
)} */}
</header>
);
}
+25 -25
View File
@@ -1,19 +1,19 @@
'use client';
"use client";
import { Icon } from '@/ui/Icon';
import { Title } from '@/ui/Title';
import { useTranslations } from 'next-intl';
import { Slider } from './Slider';
import { CityPoint } from './CityPoint';
import { enCities, enCitiesMobile } from '@/consts/cities';
import { useEffect, useRef, useState } from 'react';
import { useMediaQueries } from '@/hooks/useMediaQueries';
import { useCityPointStore } from '@/stores/useCityPointStore';
import { useGetMapPointByCity } from '@/queries/getMapPointByCity';
import { enMapProject } from '@/consts/mockEnMapProject';
import { Icon } from "@/ui/Icon";
import { Title } from "@/ui/Title";
import { useTranslations } from "next-intl";
import { Slider } from "./Slider";
import { CityPoint } from "./CityPoint";
import { enCities, enCitiesMobile } from "@/consts/cities";
import { useEffect, useRef, useState } from "react";
import { useMediaQueries } from "@/hooks/useMediaQueries";
import { useCityPointStore } from "@/stores/useCityPointStore";
import { useGetMapPointByCity } from "@/queries/getMapPointByCity";
import { enMapProject } from "@/consts/mockEnMapProject";
function NewMap() {
const t = useTranslations('map');
const t = useTranslations("map");
const [currentHovered, setCurrentHovered] = useState<number | undefined>();
@@ -24,14 +24,14 @@ function NewMap() {
const { isLg } = useMediaQueries();
return (
<div className='relative mt-[140px] flex flex-col lg:gap-16 max-lg:gap-2'>
<div className="relative mt-[140px] flex flex-col lg:gap-16 max-lg:gap-2">
<Title>
Over 15 years of work, we have completed{' '}
<span className='text-gradient'>49 projects for developers</span> in the
field of interactive technologies.
Over 15 years of work, we have completed{" "}
<span className="text-gradient">49 projects for developers</span> in the
field of interactive technologies
</Title>
<div className='max-lg:overflow-x-auto overflow-y-visible h-full scrollbar-hide md:max-lg:-mx-4 max-md:-mx-2.5 mt-16 relative'>
<div className='bg-[url(/img/pages/home/stats/en_map1.png)] max-lg:w-[90vw] max-lg:h-[100vw] bg-no-repeat bg-contain w-[62.967vw] h-[50vw] relative lg:left-[23.194vw]'>
<div className="max-lg:overflow-x-auto overflow-y-visible h-full scrollbar-hide md:max-lg:-mx-4 max-md:-mx-2.5 mt-16 relative">
<div className="bg-[url(/img/pages/home/stats/en_map1.png)] max-lg:w-[90vw] max-lg:h-[100vw] bg-no-repeat bg-contain w-[62.967vw] h-[50vw] relative lg:left-[23.194vw]">
{(isLg ? enCities : enCitiesMobile).map((point, index) => (
<CityPoint
key={point.title}
@@ -56,15 +56,15 @@ function NewMap() {
}
/>
<div className='lg:hidden absolute left-[50vw] bottom-[4.444vw] translate-y-1/2 -translate-x-1/2 w-[68.611vw] aspect-[247/56] rounded-[4.444vw] bg-[#37393B99] px-[4.167vw] py-[4.444vw] flex gap-[2.222vw] justify-between items-center'>
<div className='w-[6.667vw] h-[6.667vw]'>
<div className="lg:hidden absolute left-[50vw] bottom-[4.444vw] translate-y-1/2 -translate-x-1/2 w-[68.611vw] aspect-[247/56] rounded-[4.444vw] bg-[#37393B99] px-[4.167vw] py-[4.444vw] flex gap-[2.222vw] justify-between items-center">
<div className="w-[6.667vw] h-[6.667vw]">
<Icon
name='finger_print'
svgProp={{ className: 'w-[6.667vw] h-[6.667vw]' }}
name="finger_print"
svgProp={{ className: "w-[6.667vw] h-[6.667vw]" }}
/>
</div>
<p className='caption leading-[120%] font-medium select-none'>
{t('instruction')}
<p className="caption leading-[120%] font-medium select-none">
{t("instruction")}
</p>
</div>
</div>
+48 -47
View File
@@ -1,13 +1,13 @@
/* eslint-disable @next/next/no-img-element */
'use client';
"use client";
import { StoriesModal } from '@/components/modals/StoriesModal';
import { useModalStore } from '@/stores/useModalStore';
import { Title } from '@/ui/Title';
import { useInView } from 'framer-motion';
import { useTranslations } from 'next-intl';
import { useEffect, useRef, useState } from 'react';
import { CircularProgressbar } from 'react-circular-progressbar';
import { StoriesModal } from "@/components/modals/StoriesModal";
import { useModalStore } from "@/stores/useModalStore";
import { Title } from "@/ui/Title";
import { useInView } from "framer-motion";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
import { CircularProgressbar } from "react-circular-progressbar";
export function Motivation() {
const ref = useRef<HTMLVideoElement>(null);
@@ -21,66 +21,67 @@ export function Motivation() {
const timeUpdateHandler = () =>
setProgress((video.currentTime / video.duration) * 100);
videoRef.current.addEventListener('timeupdate', timeUpdateHandler);
return () => video.removeEventListener('timeupdate', timeUpdateHandler);
videoRef.current.addEventListener("timeupdate", timeUpdateHandler);
return () => video.removeEventListener("timeupdate", timeUpdateHandler);
}, []);
const isInView = useInView(ref, {
margin: '100% 0px -85% 0px',
margin: "100% 0px -95% 0px",
});
const { setModal } = useModalStore();
const t = useTranslations('motivation');
const t = useTranslations("motivation");
return (
<div className='lg:space-y-[4.444vw] md:max-lg:space-y-[6.25vw] max-md:space-y-[11.111vw]'>
<div className="lg:space-y-[4.444vw] md:max-lg:space-y-[6.25vw] max-md:space-y-[11.111vw]">
<Title
headerLevel={1}
className='text-center min-[2560px]:max-w-2/3 min-[2560px]:mx-auto'
className="text-center min-[2560px]:max-w-2/3 min-[2560px]:mx-auto"
>
{t('develope')}
<span className='max-lg:hidden'>&nbsp;</span>
<span className='lg:hidden'> </span>
{t('easier')}
<span className='relative'>
<span className='text-[#37393B]'>{t('faster')}</span>
<span className='absolute top-[55%] -left-[2.5%] 2xl:h-1.5 h-1 w-[105%] bg-white' />
</span>{' '}
{t('cheaper')}
{t("develope")}
<span className="max-lg:hidden">&nbsp;</span>
<span className="lg:hidden"> </span>
{t("easier")}
<span className="relative">
<span className="text-[#37393B]">{t("faster")}</span>
<span className="absolute top-[55%] -left-[2.5%] 2xl:h-1.5 h-1 w-[105%] bg-white" />
</span>
<br />
{t("expensive")}
</Title>
<div className='grid lg:gap-[0.833vw] md:max-lg:gap-[1.563vw] gap-[2.222vw]'>
<div className="grid lg:gap-[0.833vw] md:max-lg:gap-[1.563vw] gap-[2.222vw]">
<div
className={`col-start-1 lg:row-span-2 max-lg:col-span-2 duration-300 transition-all ${
isInView
? 'lg:w-[97.222vw] lg:h-[50.972vw]'
: 'lg:w-[67.986vw] lg:h-[35.556vw]'
? "lg:w-[97.222vw] lg:h-[50.972vw]"
: "lg:w-[67.986vw] lg:h-[35.556vw]"
}`}
>
<video
ref={ref}
src='/videos/pages/home/showreel.mp4'
poster='/img/pages/home/motivation/showreel_poster.jpg'
src="/videos/pages/home/showreel.mp4"
poster="/img/pages/home/motivation/showreel_poster.jpg"
autoPlay
loop
muted
playsInline
className='lg:rounded-[1.111vw] rounded-2xl h-full w-full object-cover'
className="lg:rounded-[1.111vw] rounded-2xl h-full w-full object-cover"
/>
</div>
<button
// onClick={() => setModal(<StoriesModal />)}
className='flex flex-col justify-between lg:w-[28.611vw] md:max-lg:w-[47.135vw] lg:h-[15.694vw] md:max-lg:h-[29.427vw] w-[46.111vw] h-[61.111vw] lg:p-[1.667vw] p-4 lg:col-start-2 lg:row-start-1 row-start-2 bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl relative outline-none'
className="flex flex-col justify-between lg:w-[28.611vw] md:max-lg:w-[47.135vw] lg:h-[15.694vw] md:max-lg:h-[29.427vw] w-[46.111vw] h-[61.111vw] lg:p-[1.667vw] p-4 lg:col-start-2 lg:row-start-1 row-start-2 bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] lg:rounded-[1.111vw] rounded-2xl relative outline-none"
>
<p className='font-medium heading2 lg:max-w-[51%] md:max-lg:max-w-[19.271vw] self-start text-left'>
{t('integration')}
<p className="font-medium heading2 lg:max-w-[51%] md:max-lg:max-w-[19.271vw] self-start text-left">
{t("integration")}
</p>
<div className='lg:w-[11.667vw] md:max-lg:w-[21.875vw] w-[37.222vw] aspect-square md:absolute relative lg:right-[1.667vw] lg:bottom-[0.903vw] md:max-lg:right-[3.125vw] md:max-lg:bottom-[1.693vw]'>
<div className="lg:w-[11.667vw] md:max-lg:w-[21.875vw] w-[37.222vw] aspect-square md:absolute relative lg:right-[1.667vw] lg:bottom-[0.903vw] md:max-lg:right-[3.125vw] md:max-lg:bottom-[1.693vw]">
<video
ref={videoRef}
src='/videos/pages/home/story.mp4'
poster='/img/pages/home/motivation/stories_poster.jpg'
className='aspect-square object-cover w-full h-full rounded-full'
src="/videos/pages/home/story.mp4"
poster="/img/pages/home/motivation/stories_poster.jpg"
className="aspect-square object-cover w-full h-full rounded-full"
loop
playsInline
autoPlay
@@ -94,24 +95,24 @@ export function Motivation() {
? {}
: {
path: {
stroke: 'white',
strokeLinecap: 'round',
transition: 'linear',
transitionDuration: '0.3s',
stroke: "white",
strokeLinecap: "round",
transition: "linear",
transitionDuration: "0.3s",
},
}
}
className='absolute top-0 text-white rounded-full'
className="absolute top-0 text-white rounded-full"
/>
</div>
</button>
<div className='relative lg:w-[28.611vw] lg:h-[19.028vw] md:max-lg:w-[47.135vw] md:max-lg:h-[29.427vw] overflow-hidden w-[46.111vw] h-[61.111vw] bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] col-start-2 row-start-2 lg:p-[1.667vw] p-4 lg:rounded-[1.111vw] rounded-2xl flex flex-col lg:gap-[1.944vw] md:max-lg:gap-[3.125vw] gap-[3.333vw]'>
<p className='heading2 font-medium'>{t('remote_demo')}</p>
<div className='relative lg:w-[19.583vw] md:max-lg:w-[40.885vw] lg:rounded-[0.556vw] rounded-lg w-[73.333vw] md:self-center max-md:-bottom-0.5'>
<div className="relative lg:w-[28.611vw] lg:h-[19.028vw] md:max-lg:w-[47.135vw] md:max-lg:h-[29.427vw] overflow-hidden w-[46.111vw] h-[61.111vw] bg-[radial-gradient(ellipse_at_bottom_right,#7A7A7A50,transparent)] col-start-2 row-start-2 lg:p-[1.667vw] p-4 lg:rounded-[1.111vw] rounded-2xl flex flex-col lg:gap-[1.944vw] md:max-lg:gap-[3.125vw] gap-[3.333vw]">
<p className="heading2 font-medium">{t("remote_demo")}</p>
<div className="relative lg:w-[19.583vw] md:max-lg:w-[40.885vw] lg:rounded-[0.556vw] rounded-lg w-[73.333vw] md:self-center max-md:-bottom-0.5">
<img
src='/img/pages/home/motivation/remote_demo.png'
alt='remote demo'
className='!relative lg:rounded-[0.556vw] rounded-lg object-[-1px_-1px] object-contain lg:w-[19.583vw] md:max-lg:w-[40.885vw] w-[73.333vw]'
src="/img/pages/home/motivation/remote_demo.png"
alt="remote demo"
className="!relative lg:rounded-[0.556vw] rounded-lg object-[-1px_-1px] object-contain lg:w-[19.583vw] md:max-lg:w-[40.885vw] w-[73.333vw]"
/>
</div>
</div>
+24 -24
View File
@@ -1,47 +1,47 @@
import { Title } from '@/ui/Title';
import Link from 'next/link';
import ArrowMoreIcon from '../../../../public/icons/arrow_more.svg';
import { VideoPlayer } from '@/ui/VideoPlayer';
import { Title } from "@/ui/Title";
import Link from "next/link";
import ArrowMoreIcon from "../../../../public/icons/arrow_more.svg";
import { VideoPlayer } from "@/ui/VideoPlayer";
function NewStreaming() {
return (
<div className='lg:mt-[140px] max-lg:mt-[100px] flex flex-col gap-16'>
<div className="lg:mt-[140px] max-lg:mt-[100px] flex flex-col gap-16">
<Title>
Our unique <span className='text-gradient'>remote demonstration</span>{' '}
Our unique <span className="text-gradient">remote demonstration</span>{" "}
technology allows presenting an object to a customer from anywhere in
the world.
the world
</Title>
<div className='flex gap-3 max-lg:flex-col'>
<div className='px-4 pt-4 pb-4 w-[31.875vw] bg-[#232425] rounded-2xl flex flex-col gap-3 max-lg:w-full max-lg:max-h-[500px] max-lg:justify-between'>
<div className='flex flex-col gap-2'>
<p className='heading1 font-medium'>«Upside Towers»</p>
<div className='flex gap-2'>
<div className='px-2 py-1.5 flex lg:gap-[0.278vw] gap-1 items-center lg:rounded-[1.181vw] rounded-2xl [backdrop-filter:blur(12px)] bg-[#37393B99] btns'>
<div className='lg:w-[0.556vw] lg:h-[0.556vw] w-2 h-2 rounded-full m-1 bg-[#90C141]' />
<div className="flex gap-3 max-lg:flex-col">
<div className="px-4 pt-4 pb-4 w-[31.875vw] bg-[#232425] rounded-2xl flex flex-col gap-3 max-lg:w-full max-lg:max-h-[500px] max-lg:justify-between">
<div className="flex flex-col gap-2">
<p className="heading1 font-medium">«Upside Towers»</p>
<div className="flex gap-2">
<div className="px-2 py-1.5 flex lg:gap-[0.278vw] gap-1 items-center lg:rounded-[1.181vw] rounded-2xl [backdrop-filter:blur(12px)] bg-[#37393B99] btns">
<div className="lg:w-[0.556vw] lg:h-[0.556vw] w-2 h-2 rounded-full m-1 bg-[#90C141]" />
<div>Upside Development</div>
</div>
<div className='px-3 py-1.5 flex lg:gap-[0.278vw] gap-1 items-center lg:rounded-[1.181vw] rounded-2xl [backdrop-filter:blur(12px)] bg-[#37393B99] btns'>
<div className="px-3 py-1.5 flex lg:gap-[0.278vw] gap-1 items-center lg:rounded-[1.181vw] rounded-2xl [backdrop-filter:blur(12px)] bg-[#37393B99] btns">
Moscow
</div>
</div>
</div>
<div className='bg-[url(/img/components/products/streaming-notebook.png)] bg-contain bg-center bg-no-repeat w-[28.542vw] h-[24.514vw] rounded-2xl max-lg:w-full max-lg:h-[300px]'></div>
<div className="bg-[url(/img/components/products/streaming-notebook.png)] bg-contain bg-center bg-no-repeat w-[28.542vw] h-[24.514vw] rounded-2xl max-lg:w-full max-lg:h-[300px]"></div>
<Link
href={
'https://stream.graff.tech/?build=upsideTowersDev&location=a1&lang=en'
"https://stream.graff.tech/?build=upsideTowersDev&location=a1&lang=en"
}
target='_blank'
className='transition-opacity duration-500 w-[20.833vw] h-[4.167vw] bg-gradient self-center md:max-lg:rounded-2xl rounded-xl font-medium [backdrop-filter:blur(3px)] content-center text-center z-1 max-lg:w-[300px] max-lg:h-[60px]'
target="_blank"
className="transition-opacity duration-500 w-[20.833vw] h-[4.167vw] bg-gradient self-center md:max-lg:rounded-2xl rounded-xl font-medium [backdrop-filter:blur(3px)] content-center text-center z-1 max-lg:w-[300px] max-lg:h-[60px]"
>
<p className='btnl flex justify-center gap-2'>
<p className="btnl flex justify-center gap-2">
Start demonstration
<ArrowMoreIcon className='text-white w-[1.389vw] h-[1.389vw] max-lg:w-[20px] max-lg:h-[20px]' />
<ArrowMoreIcon className="text-white w-[1.389vw] h-[1.389vw] max-lg:w-[20px] max-lg:h-[20px]" />
</p>
</Link>
</div>
<div className='max-lg:h-[400px]'>
<VideoPlayer src={'/videos/pages/home/streaming.mp4'} showMutingBtn>
<p className='accent font-medium absolute top-6 left-6 w-[39.861vw] max-lg:w-[90vw] max-lg:text-[20px] '>
<div className="max-lg:h-[400px]">
<VideoPlayer src={"/videos/pages/home/streaming.mp4"} showMutingBtn>
<p className="accent font-medium absolute top-6 left-6 w-[39.861vw] max-lg:w-[90vw] max-lg:text-[20px] ">
Graff.estate stream is available on any device all you need for
a demo is an internet connection.
</p>
+18 -16
View File
@@ -1,38 +1,40 @@
'use client';
"use client";
import {
queryProjectsOptions,
useGetProjectsQuery,
} from '@/queries/getProjects';
import { Title } from '@/ui/Title';
import { getProjectsCount } from '@/utils/getProjectsCount';
import { ProjectsSection } from '../ProjectsPage/ProjectsSection';
import { queryOptions, useQuery } from '@tanstack/react-query';
import { useLocale, useTranslations } from 'next-intl';
} from "@/queries/getProjects";
import { Title } from "@/ui/Title";
import { getProjectsCount } from "@/utils/getProjectsCount";
import { ProjectsSection } from "../ProjectsPage/ProjectsSection";
import { queryOptions, useQuery } from "@tanstack/react-query";
import { useLocale, useTranslations } from "next-intl";
export function Projects() {
// const { data: projects } = useGetProjectsQuery();
const { data: projects } = useQuery(queryProjectsOptions);
const t = useTranslations("projects");
const locale = useLocale()
const locale = useLocale();
return (
<div className="lg:space-y-16 sm:space-y-12 space-y-10 lg:mt-40 mt-[100px]">
<div className="lg:flex sm:space-y-12 space-y-10">
<Title>
{t('title1')}{' '}
{t("title1")}{" "}
<span className="text-gradient">
{projects && getProjectsCount(projects?.length, locale)}{' '}
{t('title2')}
</span>{' '}
{t('title3')}
{projects && getProjectsCount(projects?.length, locale)}{" "}
{t("title2")}
</span>{" "}
{t("title3")}
</Title>
</div>
{projects && (
<ProjectsSection
projects={projects
.filter((project) => project.stage !== 6)
.slice(0, 6)}
projects={projects.filter(
(project) =>
project.englishCity === "Dubai" ||
project.englishCity === "Abu Dhabi"
)}
/>
)}
</div>
@@ -1,47 +1,50 @@
'use client';
"use client";
import { IProject } from '@/types/IProject';
import { motion } from 'framer-motion';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import ArrowRightIcon from '../../../../public/icons/arrow_right.svg';
import { AwardsCard } from './AwardsCard';
import { ProjectCard } from './ProjectCard';
import { useTranslations } from 'next-intl';
import { IProject } from "@/types/IProject";
import { motion } from "framer-motion";
import Link from "next/link";
import { usePathname } from "next/navigation";
import ArrowRightIcon from "../../../../public/icons/arrow_right.svg";
import { AwardsCard } from "./AwardsCard";
import { ProjectCard } from "./ProjectCard";
import { useTranslations } from "next-intl";
import { useEffect } from "react";
export function ProjectsSection({ projects }: { projects: IProject[] }) {
const pathname = usePathname();
const t = useTranslations("projects");
useEffect(() => {
console.log(projects);
}, [projects]);
return (
<div
className={`grid ${
pathname.startsWith('/projects') ? 'lg:grid-cols-3' : 'lg:grid-cols-4'
pathname.startsWith("/projects") ? "lg:grid-cols-3" : "lg:grid-cols-4"
} md:max-lg:grid-cols-2 gap-3`}
>
{projects.map((project) => (
<ProjectCard key={project.id} {...project} />
))}
{pathname.startsWith('/projects') && (
{pathname.startsWith("/projects") && (
<AwardsCard className="sm:col-start-2 sm:row-start-2 row-start-3" />
)}
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true, margin: '-100px' }}
viewport={{ once: true, margin: "-100px" }}
transition={{
duration: 1,
ease: [0.58, 0.12, 0.27, 0.98],
delay: 0.2,
}}
>
{pathname === '/' && (
{pathname === "/" && (
<Link
href={'/projects'}
href={"/projects"}
scroll={false}
className="lg:rounded-[1.111vw] rounded-2xl bg-[#232425] hover:bg-[#23242599] transition-colors aspect-square self-center p-5 flex items-center justify-center btnl font-medium gap-2"
>
{t('watch')}
{t("watch")}
<ArrowRightIcon className="lg:w-[1.389vw] lg:h-[1.389vw] md:max-lg:w-[2.604vw] md:max-lg:h-[2.604vw] w-5 h-5 text-white" />
</Link>
)}
+6 -6
View File
@@ -1,15 +1,15 @@
export function getCompaniesCount(count: number, locale: string) {
if (locale === 'en') {
return `${count}\xa0developer${count === 1 ? '' : 's'}`;
if (locale === "en") {
return `${count}\xa0real estate developer${count === 1 ? "" : "s"}`;
}
return `${count}\xa0застройщик${
count > 10 && count < 15
? 'ов'
? "ов"
: count % 10 === 1
? ''
? ""
: count % 10 === 2 || count % 10 === 3 || count % 10 === 4
? 'а'
: 'ов'
? "а"
: "ов"
}`;
}