diff --git a/client/package.json b/client/package.json index 8a384d5..f72ebb5 100644 --- a/client/package.json +++ b/client/package.json @@ -18,6 +18,7 @@ "react-circular-progressbar": "^2.1.0", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", + "react-input-mask": "^2.0.4", "react-rangeslider": "^2.2.0", "react-router-dom": "^6.18.0", "react-swipeable": "^7.0.1", @@ -27,6 +28,7 @@ "devDependencies": { "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react-input-mask": "^3.0.5", "@types/react-rangeslider": "^2.2.7", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", diff --git a/client/public/images/Datamining.jpg b/client/public/images/Datamining.jpg new file mode 100644 index 0000000..162ca35 Binary files /dev/null and b/client/public/images/Datamining.jpg differ diff --git a/client/public/images/VR.png b/client/public/images/VR.png new file mode 100644 index 0000000..bb7a183 Binary files /dev/null and b/client/public/images/VR.png differ diff --git a/client/public/images/VR2.jpg b/client/public/images/VR2.jpg new file mode 100644 index 0000000..017da12 Binary files /dev/null and b/client/public/images/VR2.jpg differ diff --git a/client/src/App.tsx b/client/src/App.tsx index 869af1e..976f1a4 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -22,7 +22,12 @@ import VideoSliderMobile from "./components/VideoSliderMobile"; // import { isMobile } from "react-device-detect"; import IProject from "./types/IProject"; import api from "./utils/api"; -import ArrowRightIcon from "./components/icons/ArrowRightIcon"; +import MailIcon from "./components/icons/MailIcon"; +import PhoneIcon from "./components/icons/PhoneIcon"; +import VKIcon from "./components/icons/VKIcon"; +import YouTubeIcon from "./components/icons/YouTubeIcon"; +import TelegramIcon from "./components/icons/TelegramIcon"; +import FeedbackForm from "./components/FeedbackForm"; function App() { const [selectedVideo, setSelectedVideo] = useState( @@ -49,283 +54,320 @@ function App() { }, []); return ( -
-
-
-
- - -
-
- - -
- - + <> +
+
+
+
+ +
-
-
+
+ -
-

- Интерактивный инструмент -
- продаж{" "} - - для застройщиков - -

-

- Помогаем девелоперам эффективно демонстрировать свой объект. - Продавать больше и быстрее. -

-
- -
-
- -
-
- -
-
-
- - Помогаем продавать{" "} - - проще и{" "} - - быстрее - {" "} - дороже - - -
-
-

- Мы собрали статистику{" "} - за 13 лет{" "} - работы -
- с застройщиками,{" "} - - реализовав 31 проект - -

- -
- +
+ +
-

- Graff.estate -

- -
-
-

На

-

- 18 - - % - -

-

- увеличивает конверсию из консультации в бронирование -

-
- -
-

На

-

- 12 - - % - -

-

- увеличивает конверсию из бронирования в продажу -

-
- -
-

До

-

- 2 - - раз - -

-

- сокращает время -
- реализации проекта -

-
- -
-

До

-

- 4 - - раз - -

-

- сокращает время на подготовку рекламных материалов -

+
+

+ Интерактивный инструмент +
+ продаж{" "} + + для застройщиков + +

+

+ Помогаем девелоперам эффективно демонстрировать свой объект. + Продавать больше и быстрее. +

+
+
-
-
-
- - - +
-
- +
+
+
+ + Помогаем
продавать{" "} +
{" "} + + проще
и{" "} + + быстрее + {" "} +
+ дороже +
+
+
+
+

+ Мы собрали статистику{" "} + за 13 лет{" "} + работы +
+ с застройщиками,{" "} + + реализовав 31 проект + +

- -
-
- -
-
-
- - Функциональные -
- возможности -
-

- Интерактивная презентация увлекает покупателей и предоставляет - актуальную информацию о жилом комплексе, отвечая на все вопросы - и показывая важные особенности и преимущества объекта -

+
+ +
+
-
- setSelectedVideo(video)} +

+ Graff.estate +

+ +
+
+

На

+

+ 18 + + % + +

+

+ увеличивает конверсию из консультации в бронирование +

+
+ +
+

На

+

+ 12 + + % + +

+

+ увеличивает конверсию из бронирования в продажу +

+
+ +
+

До

+

+ 2 + + раз + +

+

+ сокращает время +
+ реализации проекта +

+
+ +
+

До

+

+ 4 + + раз + +

+

+ сокращает время на подготовку рекламных материалов +

+
+
+
+ +
+
+ - setSelectedVideo(video)} + + - setSelectedVideo(video)} +
+ +
+ - setSelectedVideo(video)} - /> - setSelectedVideo(video)} - /> - setSelectedVideo(video)} - /> - setSelectedVideo(video)} - /> - setSelectedVideo(video)} - /> - -
-
- + +
+
+
+ + Функциональные +
+ + возможности + +
+

+ Интерактивная презентация увлекает покупателей и предоставляет + актуальную информацию о жилом комплексе, отвечая на все + вопросы и показывая важные особенности и преимущества объекта +

+
+ +
+ setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + setSelectedVideo(video)} + /> + + +
+
+
+ +
+ +
- -
+
+
+
+ + Graff.estate stream — +
+ + удаленная +
+ демонстрация +
+ жилого комплекса +
+
+ +
+
+
+

+ Высокий уровень графики и полное погружение покупателя в + процесс выбора квартиры +

+ +
+
+

+ Местоположение и устройство значения не имеют. Нужен только + интернет +

+
+
+
-
-
+ {/*
Graff.estate stream —{" "} @@ -362,142 +404,295 @@ function App() { Демонстрация технологии

+
*/} + +
+ + + +
+ +
+ +
-
- - - +
+
+
+ + Анализируем +
+ + поведение +
+ пользователей +
+
+

+ Система внутренней аналитики программы собирает информацию о + поведении пользователя и эффективности работы менеджеров для + создания отчета, содержащего необходимые метрики +

+
+
+

+ Полученный отчет позволяет сделать процесс демонстрации + жилого комплекса еще эффективнее +

+
+
+
-
- +
+
+
+ + Экскурсия{" "} + + в виртуальной реальности + + + +
+

+ Клиент полностью погружается в воссозданную реальность, + чувствует удобство и уровень комфорта +

+
+
+
+

+ Достаточно надеть шлем виртуальной реальности, чтобы + прогуляться, оценить и ощутить пространство +

+ +
+ +
-
- {/*
-
- - Экскурсия -
- - в виртуальной реальности - -
- -

- Клиенту достаточно надеть шлем виртуальной реальности, чтобы - прогуляться, оценить и ощутить пространство{" "} -

- - -
-
-
*/} - -
-
-
+
+
- Анализируем + Оцените эффективность интерактивного
- поведение пользователей + инструмента продаж
-

- Система внутренней аналитики программы собирает информацию о - поведении пользователя и эффективности работы менеджеров для - создания отчета, содержащего необходимые метрики -

+
+

+ Мы изучили отраслевую аналитику на рынке продаж новых квартир + и на основе данных собрали калькулятор эффективности продукта +

+

+ При использовании проектного финансирования, главное — это + время. Быстрее наполняются эскроу-счета — меньше процент за + использование заемных денег +

+
-
-

- Полученный отчет позволяет сделать процесс демонстрации - жилого комплекса еще эффективнее -

+ + + +
+
- -
-
-
+
+ Проекты +
+ {projects.map((project, index) => ( + + ))} +
+
+ +
- Оцените эффективность интерактивного -
+ Наши клиенты{" "} - инструмента продаж + в девелопменте
-
-

- Мы изучили отраслевую аналитику на рынке продаж новых квартир и - на основе данных собрали калькулятор эффективности продукта -

-

- При использовании проектного финансирования, главное — это - время. Быстрее наполняются эскроу-счета — меньше процент за - использование заемных денег -

+ + +
+
- +
+
+
+
+

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

+

+ Хотите увеличить конверсию? +
+ Давайте обсудим детали! +

+
+
-
- +
+ +
+
-
-
- Проекты -
- {projects.map((project, index) => ( - - ))} -
-
+
+
+ +
-
- - Наши клиенты{" "} - в девелопменте - - - -
- +
+
+

+ Горячая линия +

+
+
+ + + +
+
+

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

+ +
+
+
-
+ +
+
+
+
+
+ +
+
+

+ + Политика конфиденциальности + + + graff.estate + +

+

+ © 2023 GRAFF interactive. Все права защищены. +

+
+
+ +
+
+ +
+ RU +
+
+
+ +
+
+ +
+ UAE +
+
+
+
+
+
+ ); } diff --git a/client/src/components/Button.tsx b/client/src/components/Button.tsx index 435feeb..6a8b581 100644 --- a/client/src/components/Button.tsx +++ b/client/src/components/Button.tsx @@ -4,6 +4,8 @@ interface ButtonProps { children: ReactNode; icon?: JSX.Element; color?: "primary" | "secondary"; + width?: "fit" | "full"; + disabled?: boolean; className?: string; handleClick?: () => void; } @@ -12,18 +14,21 @@ function Button({ children, color = "primary", icon, + width = "fit", + disabled = false, className, handleClick, }: ButtonProps) { return (
-
-
-
+
+
+

Срок реализации

-
+

- + {isToolEnabled ? implementationPeriodEnding : oldImplementationPeriodEnding} @@ -284,18 +291,18 @@ function Calc() { {diffImplementationPeriod} {diffImplementationPeriodEnding} {" "} - вы сократили срок + вы сократили
- реализации проекта + срок реализации проекта

-
+

Месячный доход

-
+

@@ -305,7 +312,7 @@ function Calc() { млн руб. @@ -319,16 +326,18 @@ function Calc() { {diffMonthlyIncome} млн руб. {" "} - в месяц вы заработали больше + в месяц +
+ вы заработали больше

-
+

Статистика продаж

-
-
-
+
+
+

100%

{isToolEnabled ? 48 : 30}% @@ -337,7 +346,7 @@ function Calc() { {isToolEnabled ? 42 : 30}%

-
+
-
+

- Консультаций в офисе + + Консультаций в офисе + + Консультации

- Бронь квартиры + Бронь квартиры

Продажа @@ -390,6 +402,21 @@ function Calc() {

+ +
); } diff --git a/client/src/components/FeatureItem.tsx b/client/src/components/FeatureItem.tsx index 138780e..3f37837 100644 --- a/client/src/components/FeatureItem.tsx +++ b/client/src/components/FeatureItem.tsx @@ -24,12 +24,12 @@ function FeatureItem({ >
-

+

{title}

-

+

{desc}

diff --git a/client/src/components/FeedbackForm.tsx b/client/src/components/FeedbackForm.tsx new file mode 100644 index 0000000..7ef5a7f --- /dev/null +++ b/client/src/components/FeedbackForm.tsx @@ -0,0 +1,174 @@ +import ky from "ky"; +import { ChangeEvent, FormEvent, useState } from "react"; +import InputMask from "react-input-mask"; +import AsteriskIcon from "./icons/AsteriskIcon"; +import SendIcon from "./icons/SendIcon"; +import CheckGradientIcon from "./icons/CheckGradientIcon"; +import LoaderIcon from "./icons/LoaderIcon"; +import Button from "./Button"; + +function FeedbackForm() { + const [name, setName] = useState(""); + const [phone, setPhone] = useState(""); + const [email, setEmail] = useState(""); + const [description, setDescription] = useState(""); + const [isSend, setIsSend] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + async function sendMail(e: FormEvent) { + e.preventDefault(); + + setIsLoading(true); + + try { + await ky + .post(`https://estate.graff.tech/api/mail1`, { + json: { + fullname: name, + phone, + email, + request: description, + }, + }) + .json(); + + setIsSend(true); + setIsLoading(false); + } catch (error) { + setIsLoading(false); + if (error instanceof Error) { + alert(error.message); + } + } + } + + return ( +
void sendMail(e)} + > +
+ setName(e.target.value)} + className="feedback-field bg-transparent border border-[#3D425C] rounded-none lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full" + /> +

+ Имя + +

+
+ +
+ ) => + setPhone(e.target.value) + } + className={[ + "feedback-field bg-transparent border rounded-none sm:border-l-0 sm:border-t border-t-0 border-l border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full", + ].join(" ")} + /> +

+ Телефон + +

+
+ +
+ setEmail(e.target.value)} + className="feedback-field bg-transparent border rounded-none lg:border-l-0 lg:border-t border-t-0 border-[#3D425C] lg:p-6 lg:pt-14 p-4 pt-12 outline-none outline-1 -outline-offset-1 focus:outline-[#D375FF] transition-all w-full" + /> +

+ Email + +

+
+ +
+ +
+ +
+ +
+ +
+
+ Нажимая кнопку отправить, вы принимаете{" "} + + условия использования + {" "} + и{" "} + + политику конфиденциальности + +
+
+ +
+
+
+ +
+

+

+ Звездочкой отмечены обязательные +
+ для заполнения поля +

+
+
+ + {isSend && ( +
+

+ Заявка отправлена + +

+ +
+

+ Спасибо за подачу заявки! +

+ +

+ Мы ценим ваш интерес к нашей компании и в ближайшее время свяжемся + с вами для уточнения деталей проекта. +

+
+
+ )} + + ); +} + +export default FeedbackForm; diff --git a/client/src/components/Headings/Heading2.tsx b/client/src/components/Headings/Heading2.tsx index 4148744..2db4c90 100644 --- a/client/src/components/Headings/Heading2.tsx +++ b/client/src/components/Headings/Heading2.tsx @@ -5,7 +5,7 @@ interface Heading2Props { className?: string; } -function Heading2({ children, className }: Heading2Props) { +function Heading2({ children, className = "" }: Heading2Props) { return (

[ @@ -9,12 +10,16 @@ function ModalContainer() { if (modal) { return (
setModal(null)} - className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto cursor-pointer transition-opacity`} + // onClick={() => setModal(null)} + className={`min-h-screen p-8 absolute top-0 left-0 w-full flex justify-center items-center bg-black bg-opacity-30 overflow-auto transition-opacity`} >
e.stopPropagation()} className="cursor-default"> {modal}
+ +
); } diff --git a/client/src/components/ProjectCard.tsx b/client/src/components/ProjectCard.tsx index 1e346d2..ed7e433 100644 --- a/client/src/components/ProjectCard.tsx +++ b/client/src/components/ProjectCard.tsx @@ -4,6 +4,7 @@ import TouchScreenIcon from "./icons/TouchScreenIcon"; import VRIcon from "./icons/VRIcon"; import MobileIcon from "./icons/MobileIcon"; import IProject from "../types/IProject"; +import { format } from "date-fns"; function ProjectCard({ name, @@ -11,7 +12,7 @@ function ProjectCard({ city, image, stage = 6, - releaseYear = 2023, + releaseDate = format(new Date(), "yyyy-MM-dd"), devices = [], }: IProject) { const stagePercentage = Math.round((100 / 6) * stage); @@ -50,7 +51,7 @@ function ProjectCard({ ) : (

- {releaseYear} + {new Date(releaseDate).getFullYear()}

)} diff --git a/client/src/components/StreamButton.tsx b/client/src/components/StreamButton.tsx index 59c1a04..ffd28ac 100644 --- a/client/src/components/StreamButton.tsx +++ b/client/src/components/StreamButton.tsx @@ -16,34 +16,41 @@ function StreamButton({ link, }: StreamButton) { return ( -
-
- -

{title}

-
-
-

{location}

-
-
-

Демоверсия

- -
-
+ -
-

- начать демонстрацию -

-

{title}

+
+ +

{title}

+
+

{location}

+
+
+

Демоверсия

+ +
+
+
+

+ начать демонстрацию +

+

{title}

+
+
+
+ +
+
test 1
+
test 2
- + ); } diff --git a/client/src/components/VideoSliderMobile.tsx b/client/src/components/VideoSliderMobile.tsx index 1dd2a08..a00f2e8 100644 --- a/client/src/components/VideoSliderMobile.tsx +++ b/client/src/components/VideoSliderMobile.tsx @@ -104,7 +104,10 @@ function VideoSliderMobile() { }, [activeIndex]); return ( -
+
{items.map((item, index) => ( ))} diff --git a/client/src/components/icons/AsteriskIcon.tsx b/client/src/components/icons/AsteriskIcon.tsx new file mode 100644 index 0000000..ee2f4ca --- /dev/null +++ b/client/src/components/icons/AsteriskIcon.tsx @@ -0,0 +1,18 @@ +function AsteriskIcon() { + return ( + + + + ); +} + +export default AsteriskIcon; diff --git a/client/src/components/icons/CheckGradientIcon.tsx b/client/src/components/icons/CheckGradientIcon.tsx new file mode 100644 index 0000000..0f13fb5 --- /dev/null +++ b/client/src/components/icons/CheckGradientIcon.tsx @@ -0,0 +1,41 @@ +interface IconProps { + className?: string; +} + +function CheckGradientIcon({ className }: IconProps) { + return ( + + + + + + + + + + + + ); +} + +export default CheckGradientIcon; diff --git a/client/src/components/icons/CloseIcon.tsx b/client/src/components/icons/CloseIcon.tsx index fd6ce0e..7b5c290 100644 --- a/client/src/components/icons/CloseIcon.tsx +++ b/client/src/components/icons/CloseIcon.tsx @@ -1,4 +1,8 @@ -function CloseIcon() { +interface IconProps { + className?: string; +} + +function CloseIcon({ className }: IconProps) { return ( + + + + + + + + + + + ); +} + +export default LoaderIcon; diff --git a/client/src/components/icons/MailIcon.tsx b/client/src/components/icons/MailIcon.tsx new file mode 100644 index 0000000..1f6b6fb --- /dev/null +++ b/client/src/components/icons/MailIcon.tsx @@ -0,0 +1,31 @@ +interface IconProps { + className?: string; +} + +function MailIcon({ className }: IconProps) { + return ( + + + + + + + + + ); +} + +export default MailIcon; diff --git a/client/src/components/icons/PhoneIcon.tsx b/client/src/components/icons/PhoneIcon.tsx new file mode 100644 index 0000000..7dd8686 --- /dev/null +++ b/client/src/components/icons/PhoneIcon.tsx @@ -0,0 +1,26 @@ +interface IconProps { + className?: string; +} + +function PhoneIcon({ className }: IconProps) { + return ( + + + + + + ); +} + +export default PhoneIcon; diff --git a/client/src/components/icons/SendIcon.tsx b/client/src/components/icons/SendIcon.tsx new file mode 100644 index 0000000..a75735f --- /dev/null +++ b/client/src/components/icons/SendIcon.tsx @@ -0,0 +1,28 @@ +interface IconProps { + className?: string; +} + +function SendIcon({ className }: IconProps) { + return ( + + + + + + ); +} + +export default SendIcon; diff --git a/client/src/components/icons/TelegramIcon.tsx b/client/src/components/icons/TelegramIcon.tsx new file mode 100644 index 0000000..bcf6d57 --- /dev/null +++ b/client/src/components/icons/TelegramIcon.tsx @@ -0,0 +1,28 @@ +interface IconProps { + className?: string; +} + +function TelegramIcon({ className }: IconProps) { + return ( + + + + + + ); +} + +export default TelegramIcon; diff --git a/client/src/components/icons/VKIcon.tsx b/client/src/components/icons/VKIcon.tsx new file mode 100644 index 0000000..bd27a52 --- /dev/null +++ b/client/src/components/icons/VKIcon.tsx @@ -0,0 +1,28 @@ +interface IconProps { + className?: string; +} + +function VKIcon({ className }: IconProps) { + return ( + + + + + + ); +} + +export default VKIcon; diff --git a/client/src/components/icons/YouTubeIcon.tsx b/client/src/components/icons/YouTubeIcon.tsx new file mode 100644 index 0000000..a673b85 --- /dev/null +++ b/client/src/components/icons/YouTubeIcon.tsx @@ -0,0 +1,28 @@ +interface IconProps { + className?: string; +} + +function YouTubeIcon({ className }: IconProps) { + return ( + + + + + + ); +} + +export default YouTubeIcon; diff --git a/client/src/components/modals/CreateProjectModal.tsx b/client/src/components/modals/CreateProjectModal.tsx index 4242a2e..b3b3a6f 100644 --- a/client/src/components/modals/CreateProjectModal.tsx +++ b/client/src/components/modals/CreateProjectModal.tsx @@ -5,6 +5,7 @@ import api from "../../utils/api"; import Button from "../Button"; import IProject from "../../types/IProject"; import useModalStore from "../../stores/useModalStore"; +import { format } from "date-fns"; function CreateProjectModal() { const [project, setProject] = useState({ @@ -12,6 +13,7 @@ function CreateProjectModal() { company: "", city: "", image: "", + releaseDate: format(new Date(), "yyyy-MM-dd"), devices: [], }); @@ -157,34 +159,19 @@ function CreateProjectModal() {
- - - setProject((prev) => ({ ...prev, releaseYear: +e.target.value })) + setProject((prev) => ({ + ...prev, + releaseDate: e.target.value, + })) } - > - - - - - - - - - - - - - - - - - + className="border border-neutral-500 px-3 py-2 rounded-lg outline-none" + />
@@ -281,9 +268,7 @@ function CreateProjectModal() {
- +
diff --git a/client/src/components/modals/EditProjectModal.tsx b/client/src/components/modals/EditProjectModal.tsx index 0cc0376..79e5b82 100644 --- a/client/src/components/modals/EditProjectModal.tsx +++ b/client/src/components/modals/EditProjectModal.tsx @@ -5,6 +5,7 @@ import api from "../../utils/api"; import Button from "../Button"; import IProject from "../../types/IProject"; import useModalStore from "../../stores/useModalStore"; +import { format, parseISO } from "date-fns"; interface EditProjectModalProps { projectId: string; @@ -16,6 +17,7 @@ function EditProjectModal({ projectId }: EditProjectModalProps) { company: "", city: "", image: "", + releaseDate: "2023-01-01", devices: [], }); @@ -72,6 +74,7 @@ function EditProjectModal({ projectId }: EditProjectModalProps) { async function getProject() { try { const project: IProject = await api.get(`projects/${projectId}`).json(); + project.releaseDate = format(parseISO(project.releaseDate), "yyyy-MM-dd"); setProject(project); } catch (error) { @@ -177,34 +180,19 @@ function EditProjectModal({ projectId }: EditProjectModalProps) {
- - - setProject((prev) => ({ ...prev, releaseYear: +e.target.value })) + setProject((prev) => ({ + ...prev, + releaseDate: e.target.value, + })) } - > - - - - - - - - - - - - - - - - - + className="border border-neutral-500 px-3 py-2 rounded-lg outline-none" + />
diff --git a/client/src/index.css b/client/src/index.css index 51f17ba..3c20838 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -60,10 +60,6 @@ body { /* Custom Line Through */ -.custom-line-through { - position: relative; -} - .custom-line-through::before { content: ""; position: absolute; @@ -91,3 +87,25 @@ body { .button { @apply 2xl:text-[40px] xl:text-2xl sm:text-base text-sm; } + +/* Feedback Form */ + +.feedback-field:focus ~ .feedback-placeholder { + top: 0; +} + +.feedback-field:focus ~ .feedback-placeholder-2 { + opacity: 0; +} + +.feedback-field:valid ~ .feedback-placeholder { + top: 0; +} + +.feedback-field:valid ~ .feedback-placeholder-2 { + opacity: 0; +} + +.feedback-field::placeholder { + @apply lg:text-base text-sm font-semibold text-[#77787d]; +} diff --git a/client/src/main.tsx b/client/src/main.tsx index 9e84bb2..a0f54b7 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -3,6 +3,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import App from "./App.tsx"; import "./index.css"; import ProjectsPage from "./pages/ProjectsPage.tsx"; +import TestPage from "./pages/TestPage.tsx"; const router = createBrowserRouter([ { @@ -13,6 +14,10 @@ const router = createBrowserRouter([ path: "/projects", element: , }, + { + path: "/test", + element: , + }, ]); ReactDOM.createRoot(document.getElementById("root")!).render( diff --git a/client/src/pages/TestPage.tsx b/client/src/pages/TestPage.tsx new file mode 100644 index 0000000..c734692 --- /dev/null +++ b/client/src/pages/TestPage.tsx @@ -0,0 +1,11 @@ +function TestPage() { + return ( + + ); +} + +export default TestPage; diff --git a/client/src/types/IProject.ts b/client/src/types/IProject.ts index 92253cc..890c3e1 100644 --- a/client/src/types/IProject.ts +++ b/client/src/types/IProject.ts @@ -7,8 +7,8 @@ interface IProject { city: string; image: string; stage?: number; - releaseYear?: number; - devices?: Device[]; + releaseDate: string; + devices: Device[]; } export default IProject; diff --git a/client/vite.config.ts b/client/vite.config.ts index 861b04b..d366e8c 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react-swc' +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) +}); diff --git a/client/yarn.lock b/client/yarn.lock index 5dea54f..c100d0e 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -346,6 +346,13 @@ dependencies: "@types/react" "*" +"@types/react-input-mask@^3.0.5": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/react-input-mask/-/react-input-mask-3.0.5.tgz#9fbe9a984b3299419a6071dbf697ac2cae2abd2d" + integrity sha512-vQ1x6ykwjDrDrJZq1zw5/uQ+nqGHUV6bWscsVZJ/qsNwNXWxZm7KRBHLJ5k6TQt3MHjhpoYHzPH6FwjVSZODHA== + dependencies: + "@types/react" "*" + "@types/react-rangeslider@^2.2.7": version "2.2.7" resolved "https://registry.yarnpkg.com/@types/react-rangeslider/-/react-rangeslider-2.2.7.tgz#13f8edcec3f09b6c6b7669828e2765d294ce0c53" @@ -1049,6 +1056,13 @@ inherits@2: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1164,7 +1178,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -1429,6 +1443,14 @@ react-dom@^18.2.0: loose-envify "^1.1.0" scheduler "^0.23.0" +react-input-mask@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-input-mask/-/react-input-mask-2.0.4.tgz#9ade5cf8196f4a856dbf010820fe75a795f3eb14" + integrity sha512-1hwzMr/aO9tXfiroiVCx5EtKohKwLk/NT8QlJXHQ4N+yJJFyUuMT+zfTpLBwX/lK3PkuMlievIffncpMZ3HGRQ== + dependencies: + invariant "^2.2.4" + warning "^4.0.2" + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -1738,6 +1760,13 @@ vite@^4.4.5: optionalDependencies: fsevents "~2.3.2" +warning@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" diff --git a/server/src/models/Project.ts b/server/src/models/Project.ts index 419490b..965ef2d 100644 --- a/server/src/models/Project.ts +++ b/server/src/models/Project.ts @@ -22,8 +22,8 @@ const projectSchema = new Schema( type: Number, default: 1, }, - releaseYear: { - type: Number, + releaseDate: { + type: Date, required: true, }, devices: { diff --git a/server/uploads/a5fe12a2-75f3-4de7-a765-18316cd58d3f.jpg b/server/uploads/a5fe12a2-75f3-4de7-a765-18316cd58d3f.jpg deleted file mode 100644 index f627bb6..0000000 Binary files a/server/uploads/a5fe12a2-75f3-4de7-a765-18316cd58d3f.jpg and /dev/null differ