From cf2e539fa8acb71d529d3b1a6d59b3e6de380e6f Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Thu, 5 Sep 2024 17:38:43 +0500 Subject: [PATCH] fixes and updates --- index.html | 22 +- package.json | 5 +- src/components/Layouts/Footer.tsx | 2 +- src/components/Layouts/Header.tsx | 71 ++-- src/components/Layouts/Layout.tsx | 4 +- .../Layouts/ScrollToHashElement.tsx | 19 + src/components/Main/Availables.tsx | 26 +- src/components/Main/Clients.tsx | 28 +- src/components/Main/Contacts.tsx | 10 +- src/components/Main/Decreasing.tsx | 2 +- src/components/Main/Distance.tsx | 8 +- src/components/Main/Efficiency.tsx | 10 +- src/components/Main/Ellipse.tsx | 8 +- src/components/Main/Events.tsx | 12 +- src/components/Main/ModalContainer.tsx | 2 +- src/components/Main/ModalWithForm.tsx | 340 +++++++++++++++--- src/components/Main/Motivation.tsx | 5 +- src/components/Main/Products/Products.tsx | 2 +- .../Main/Products/Tabs/ForTeachingTab.tsx | 70 ++-- .../Main/Products/Tabs/TrainingsTab.tsx | 16 +- src/components/Main/Projects.tsx | 16 +- src/components/Main/ProjectsSlider.tsx | 5 +- src/components/Main/RecentProjects.tsx | 20 +- src/components/Main/SelectPhoneCode.tsx | 75 ++++ src/components/Main/SliderControls.tsx | 2 +- src/components/Main/Teaching.tsx | 6 +- src/components/Main/Trainings.tsx | 12 +- src/components/Main/Video.tsx | 10 +- src/components/icons/DangerIcon.tsx | 9 +- src/components/icons/LaboratoriesIcon.tsx | 9 +- src/components/icons/PlansIcon.tsx | 9 +- src/components/icons/ProcessesIcon.tsx | 9 +- src/components/icons/SafetyIcon.tsx | 9 +- src/components/icons/ServiceIcon.tsx | 9 +- src/components/icons/TeachSystemsIcon.tsx | 9 +- src/components/icons/TeamworkIcon.tsx | 9 +- src/hocs/ClassNameWrapper.tsx | 20 ++ src/ui/AnchorLink.tsx | 22 +- src/ui/Button.tsx | 2 +- src/ui/SliderWithScaling.tsx | 2 +- src/utils/getIcon.ts | 41 ++- tailwind.config.js | 2 +- yarn.lock | 49 ++- 43 files changed, 684 insertions(+), 334 deletions(-) create mode 100644 src/components/Layouts/ScrollToHashElement.tsx create mode 100644 src/components/Main/SelectPhoneCode.tsx create mode 100644 src/hocs/ClassNameWrapper.tsx diff --git a/index.html b/index.html index 585fe5a..59a3d94 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,25 @@ - + - - - + + + + + Интерактивные тренажеры для промышленности и образования diff --git a/package.json b/package.json index 3579921..0a36fee 100644 --- a/package.json +++ b/package.json @@ -10,16 +10,17 @@ "preview": "vite preview" }, "dependencies": { + "countries-phone-masks": "^1.1.0", "framer-motion": "^11.2.14", "ky": "^1.4.0", + "libphonenumber-js": "^1.11.7", "react": "^18.3.1", "react-dom": "^18.3.1", "react-input-mask": "^2.0.4", + "react-phone-number-input": "^3.4.5", "react-rangeslider": "^2.2.0", "react-router-dom": "^6.23.1", - "react-router-hash-link": "^2.4.3", "react-swipeable": "^7.0.1", - "react-usestateref": "^1.0.9", "usehooks-ts": "^3.1.0", "zustand": "^4.5.4" }, diff --git a/src/components/Layouts/Footer.tsx b/src/components/Layouts/Footer.tsx index fa81d2b..89b2d2a 100644 --- a/src/components/Layouts/Footer.tsx +++ b/src/components/Layouts/Footer.tsx @@ -11,7 +11,7 @@ export function Footer() {
Политика конфиденциальности graff.tech diff --git a/src/components/Layouts/Header.tsx b/src/components/Layouts/Header.tsx index c05c1c9..26a1eeb 100644 --- a/src/components/Layouts/Header.tsx +++ b/src/components/Layouts/Header.tsx @@ -1,9 +1,8 @@ -import { motion } from 'framer-motion'; +import { AnimatePresence, motion } from 'framer-motion'; import { PropsWithChildren, useRef, useState } from 'react'; import { AnchorLink } from '../../ui/AnchorLink'; import { Link } from 'react-router-dom'; import { Lang, useLanguageStore } from '../../store/languageStore'; -import { HashLink } from 'react-router-hash-link'; import { useHover, useOnClickOutside } from 'usehooks-ts'; import { useModalStore } from '../../store/modalStore'; import { ModalWithForm } from '../Main/ModalWithForm'; @@ -37,26 +36,25 @@ export function Header() { {width >= 1024 && } -
+
Типы тренажеров Варианты комплектации Проекты - События - - -
+ + + Типы тренажеров Варианты комплектации Проекты - События
@@ -102,13 +99,13 @@ function BurgerAnchor({ route, }: PropsWithChildren<{ route: string }>) { return ( - {children} - + ); } @@ -138,28 +135,32 @@ function LangToggler({ lang }: { lang: Lang }) { return (
- setOpen(false)} - initial={{ visibility: 'hidden' }} - animate={{ - visibility: open ? 'visible' : 'hidden', - }} - > - - - + + setOpen(false)} + initial={{ visibility: 'hidden', opacity: 0 }} + animate={{ + visibility: open ? 'visible' : 'hidden', + opacity: +open, + }} + exit={{ visibility: 'hidden', opacity: 0 }} + > + + + +
); } diff --git a/src/components/Layouts/Layout.tsx b/src/components/Layouts/Layout.tsx index 7e9f2e5..41a686d 100644 --- a/src/components/Layouts/Layout.tsx +++ b/src/components/Layouts/Layout.tsx @@ -4,12 +4,14 @@ import { Footer } from './Footer'; import { ModalContainer } from '../Main/ModalContainer'; import { Feedback } from '../Main/Contacts'; import { Clients } from '../Main/Clients'; +import { ScrollToHashElement } from './ScrollToHashElement'; export function Layout() { return ( <> +
-
+
diff --git a/src/components/Layouts/ScrollToHashElement.tsx b/src/components/Layouts/ScrollToHashElement.tsx new file mode 100644 index 0000000..7d9fb1b --- /dev/null +++ b/src/components/Layouts/ScrollToHashElement.tsx @@ -0,0 +1,19 @@ +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; + +export function ScrollToHashElement() { + const { hash } = useLocation(); + + useEffect(() => { + const hashElement = document.getElementById(hash.slice(1)); + + if (hashElement) + hashElement.scrollIntoView({ + behavior: 'smooth', + inline: 'nearest', + block: 'start', + }); + }, [hash]); + + return null; +} diff --git a/src/components/Main/Availables.tsx b/src/components/Main/Availables.tsx index ac48e53..d43567c 100644 --- a/src/components/Main/Availables.tsx +++ b/src/components/Main/Availables.tsx @@ -7,13 +7,14 @@ import { getIcon } from '../../utils/getIcon'; export function Availables() { return ( -
- + <div className="lg:py-[100px] py-14 sm:grid lg:grid-cols-12 sm:grid-rows-[repeat(4,fit)] sm:grid-cols-3"> + <Title className="row-start-1 mb-6 lg:mb-14 col-span-full"> <span className="text-gradient text-wrap">Многопользовательский </span> + <br className="lg:hidden" /> режим обучения -
-
+
+
+
(null); const hovered = useHover(ref); const isInView = useInView(ref, { - margin: `0px 0px -${window.innerHeight - (ref.current?.clientHeight ?? 0)}px`, + margin: `0px 0px ${(ref.current?.clientHeight ?? 0) - window.innerHeight}px`, }); return (
- {getIcon(type, hovered, 'mb-4 max-sm:hidden w-14 h-14')} - {getIcon(type, isInView, 'mb-4 sm:hidden w-14 h-14')} +
+ {getIcon(type, hovered, 'mb-4 max-lg:hidden w-14 h-14')} +
+
+ {getIcon(type, isInView, 'mb-4 lg:hidden w-14 h-14')} +

{text}

); diff --git a/src/components/Main/Clients.tsx b/src/components/Main/Clients.tsx index 9266b30..2e01133 100644 --- a/src/components/Main/Clients.tsx +++ b/src/components/Main/Clients.tsx @@ -2,12 +2,12 @@ import { clients } from '../../consts/clients'; export function Clients() { return ( -
-
+
+
-
+
-
+
@@ -35,17 +35,21 @@ function MarqueeHalf({ return (
{items.map(client => ( - {client.src} +
+ {client.src} +
))}
); diff --git a/src/components/Main/Contacts.tsx b/src/components/Main/Contacts.tsx index c6c1f55..963b749 100644 --- a/src/components/Main/Contacts.tsx +++ b/src/components/Main/Contacts.tsx @@ -10,7 +10,7 @@ import { YouTubeIcon } from '../icons/YoutubeIcon'; export function Feedback() { return (
-

+

Хотите использовать интерактивные тренажеры в обучении?
Давайте обсудим детали. @@ -18,13 +18,13 @@ export function Feedback() {
-

Свяжитесь с нами

+

Свяжитесь с нами

-
-

Социальные сети

+
+

Социальные сети

-
+
diff --git a/src/components/Main/Distance.tsx b/src/components/Main/Distance.tsx index 4a70a88..a670999 100644 --- a/src/components/Main/Distance.tsx +++ b/src/components/Main/Distance.tsx @@ -3,13 +3,13 @@ import { Title } from '../../ui/Title'; export function Distance() { return (
- + <Title className="mb-6 lg:mb-14"> Платформа GRAFF.training поволяет осуществлять <span className="text-gradient"> дистанционное обучение</span> с любого устройства
-

+

Обучающиеся будут получать доступ к системе с высоко детализированной 3D графикой для прохождения сценариев на любом устройстве, без необходимости установки дополнительного ПО. @@ -17,12 +17,12 @@ export function Distance() { дистанционное обучение с любого устройства дистанционное обучение с любого устройства

diff --git a/src/components/Main/Efficiency.tsx b/src/components/Main/Efficiency.tsx index a7f5ece..f4fb05b 100644 --- a/src/components/Main/Efficiency.tsx +++ b/src/components/Main/Efficiency.tsx @@ -84,17 +84,17 @@ function Figure({

{title}

-

+

{percents} % diff --git a/src/components/Main/Ellipse.tsx b/src/components/Main/Ellipse.tsx index 51ba377..733118f 100644 --- a/src/components/Main/Ellipse.tsx +++ b/src/components/Main/Ellipse.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; export function Ellipse() { const ref = useRef(null); @@ -14,7 +14,7 @@ export function Ellipse() { e.clientY - (e.currentTarget as HTMLElement).getBoundingClientRect().top; } - function animate() { + const animate = useCallback(() => { if (ref.current) { setMousePos(([prevX, prevY]) => [ prevX + (x.current - prevX) / 15, @@ -22,7 +22,7 @@ export function Ellipse() { ]); } requestRef.current = requestAnimationFrame(animate); - } + }, []); useEffect(() => { document.body?.addEventListener('mousemove', handleMouseMove); @@ -32,7 +32,7 @@ export function Ellipse() { cancelAnimationFrame(requestRef.current!); document.body?.removeEventListener('mousemove', handleMouseMove); }; - }, []); + }, [animate]); return (
-
- +
+
-
+
Макет кабины машиниста «Иволга» на выставке ВДНХ @@ -35,12 +35,12 @@ export function Events() {
-
+
Победа на BuildUP 2023 в номинации IT
-
+
Транспортное и специальное тренажеростроение — 2023 @@ -65,7 +65,7 @@ function LinkButton({ href }: { href: string }) { const hovered = useHover(ref); return ( -
+
+
e.stopPropagation()} className="cursor-default"> {modal}
diff --git a/src/components/Main/ModalWithForm.tsx b/src/components/Main/ModalWithForm.tsx index e161b0b..7073422 100644 --- a/src/components/Main/ModalWithForm.tsx +++ b/src/components/Main/ModalWithForm.tsx @@ -1,29 +1,39 @@ -'use client'; - -import { FormEvent, useEffect, useRef, useState } from 'react'; +import { FormEvent, useEffect, useMemo, useRef, useState } from 'react'; import ReactInputMask from 'react-input-mask'; import { ArrowRightIcon } from '../icons/ArrowRightIcon'; import { CloseIcon } from '../icons/CloseIcon'; import { LoaderIcon } from '../icons/LoaderIcon'; import { MailIcon } from '../icons/MailIcon'; -import { phoneCodes } from '../../consts/phoneCodes'; import { api } from '../../api/contactsFormInstance'; import { Button } from '../../ui/Button'; -import { PhoneCode } from '../../types/PhoneCode'; -import { ChevronUpIcon } from '../icons/ChevronUpIcon'; -import { ChevronDownIcon } from '../icons/ChevronDownIcon'; import { useModalStore } from '../../store/modalStore'; +import { SelectPhoneCode } from './SelectPhoneCode'; +import { Country } from 'react-phone-number-input'; +import { getExampleNumber } from 'libphonenumber-js'; +import examples from 'libphonenumber-js/mobile/examples'; export function ModalWithForm() { const { setModal } = useModalStore(); const [name, setName] = useState(''); - const [phoneCode, setPhoneCode] = useState('+7'); + const [[phoneCode, country], setPhoneCodeAndCountry] = useState< + [string, Country] + >(['+7', 'RU']); const [phone, setPhone] = useState(''); const [email, setEmail] = useState(''); const [description, setDescription] = useState(''); const [isLoading, setIsLoading] = useState(false); const [isSend, setIsSend] = useState(false); + const placeholder = useMemo( + () => + getExampleNumber(country, examples) + ?.formatInternational() + .split(' ') + .slice(1) + .join(' '), + [country], + ); + const textAreaRef = useRef(null); useEffect(() => { @@ -79,11 +89,11 @@ export function ModalWithForm() {
{!isSend ? (
-
+

Оставьте заявку

@@ -119,20 +129,20 @@ export function ModalWithForm() {
setPhone(e.target.value)} - className="bg-transparent rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none peer" + placeholder={placeholder} + onChange={e => setPhone(e.target.value.replace(/ /g, ''))} + className="w-full transition-all bg-transparent rounded-none outline-none h4 placeholder:h4 placeholder:font-medium placeholder:select-none peer" />
@@ -201,7 +211,7 @@ export function ModalWithForm() { ) : (
-

Спасибо за отправку заявки!

+

Спасибо за отправку заявки!

Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся с вами для уточнения деталей проекта. @@ -221,45 +231,257 @@ export function ModalWithForm() { ); } -function SelectPhoneCode({ - currentPhoneCode, - onClick, -}: { - currentPhoneCode: PhoneCode; - onClick: (phoneCode: PhoneCode) => void; -}) { - const [open, setOpen] = useState(false); +// function SelectPhoneCode({ +// currentPhoneCode, +// onClick, +// }: { +// currentPhoneCode: PhoneCode; +// onClick: (phoneCode: PhoneCode) => void; +// }) { +// const [open, setOpen] = useState(false); - return ( -

- - {open && ( -
- {phoneCodes - .filter(phonecode => phonecode !== currentPhoneCode) - .map(phoneCode => ( -

{ - onClick(phoneCode); - setOpen(false); - }} - > - {phoneCode} -

- ))} -
- )} -
- ); -} +// return ( +//
+// +// {open && ( +//
+// {phoneCodes +// .filter(phonecode => phonecode !== currentPhoneCode) +// .map(phoneCode => ( +//

{ +// onClick(phoneCode); +// setOpen(false); +// }} +// > +// {phoneCode} +//

+// ))} +//
+// )} +//
+// ); +// } +// import ReactInputMask from 'react-input-mask'; +// import { SelectPhoneCode } from './SelectPhoneCode'; +// import { ClassNameWrapper } from '../../hocs/ClassNameWrapper'; +// import { LoaderIcon } from '../icons/LoaderIcon'; +// import { Button } from '../../ui/Button'; +// import { ArrowRightIcon } from '../icons/ArrowRightIcon'; +// import { FormEvent, useEffect, useMemo, useRef, useState } from 'react'; +// import { Country } from 'react-phone-number-input'; +// import { api } from '../../api/contactsFormInstance'; +// import { getExampleNumber } from 'libphonenumber-js'; +// import examples from 'libphonenumber-js/mobile/examples'; + +// export function ModalWithForm({ +// inModal = true, +// send = () => {}, +// }: { +// inModal?: boolean; +// send?: () => void; +// }) { +// const [name, setName] = useState(''); +// const [[phoneCode, country], setPhoneCodeAndCountry] = useState< +// [string, Country] +// >(['+7', 'RU']); +// const [phone, setPhone] = useState(''); +// const [email, setEmail] = useState(''); +// const [description, setDescription] = useState(''); +// const [isLoading, setIsLoading] = useState(false); + +// const textAreaRef = useRef(null); + +// useEffect(() => { +// if (textAreaRef.current) { +// textAreaRef.current.style.height = 'auto'; +// textAreaRef.current.style.height = +// textAreaRef.current.scrollHeight + 'px'; +// } +// }, [textAreaRef, description]); + +// function handleSubmit(e: FormEvent) { +// e.preventDefault(); +// sendMail(); +// } + +// async function sendMail() { +// setIsLoading(true); + +// try { +// await api +// .post('mail', { +// json: { +// fullname: name, +// phone: phoneCode + phone, +// email, +// request: description, +// }, +// }) +// .json(); + +// setIsLoading(false); +// send?.(); +// } catch (error) { +// setIsLoading(false); +// if (error instanceof Error) { +// alert(error.message); +// } +// } +// } + +// const placeholder = useMemo( +// () => +// getExampleNumber(country, examples) +// ?.formatInternational() +// .split(' ') +// .slice(1) +// .join(' '), +// [country], +// ); + +// return ( +//
+//
+//
+//
+// +// setName(e.target.value)} +// placeholder="Ваше имя" +// className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none" +// /> +//
+ +//
+// +// setEmail(e.target.value)} +// placeholder="Ваш email" +// className="bg-transparent border-b border-[#3D425C] focus:border-white py-4 rounded-none outline-none transition-all w-full placeholder:h4 placeholder:font-medium placeholder:select-none" +// /> +//
+ +//
+// +//
+// +//
+// setPhone(e.target.value.replace(/ /g, ''))} +// className="w-full transition-all bg-transparent rounded-none outline-none h4 placeholder:h4 placeholder:font-medium placeholder:select-none peer" +// /> +//
+//
+//
+//
+ +//
+// +//