618 lines
24 KiB
JavaScript
618 lines
24 KiB
JavaScript
import React, { useRef, useState } from "react";
|
||
import SVG from "react-inlinesvg";
|
||
|
||
import { AuthHeader } from "@components/Common/AuthHeader/AuthHeader";
|
||
import { Footer } from "@components/Common/Footer/Footer";
|
||
|
||
import arrowReviewsLeft from "assets/icons/arrows/arrowReviewsLeft.png";
|
||
import arrowReviewsRight from "assets/icons/arrows/arrowReviewsRight.png";
|
||
import fileUploadIcon from "assets/icons/fileUploadIcon.svg";
|
||
import Ellipse from "assets/images/EllipseIntro.svg";
|
||
import backgroundOpp from "assets/images/backgroundOpportunity.png";
|
||
import bagITguild from "assets/images/bagITguild.png";
|
||
import cat from "assets/images/cat.png";
|
||
import clue from "assets/images/clue.png";
|
||
import code from "assets/images/landingBackgroundCode.png";
|
||
import codeWhite from "assets/images/landingBackgroundCodeWhite.png";
|
||
import reviewsImgMok from "assets/images/reviewsImgMok.png";
|
||
import flag from "assets/images/stackProjectsFlag.png";
|
||
import fly from "assets/images/stackProjectsFly.png";
|
||
import hand from "assets/images/stackProjectsHand.png";
|
||
import rabota from "assets/images/stackProjectsRabota.png";
|
||
import portfolio from "assets/images/stackSteptsPortfolio.png";
|
||
|
||
import "./stack.scss";
|
||
|
||
export const Stack = () => {
|
||
const subjects = [
|
||
{
|
||
name: "Backend",
|
||
skills: [
|
||
"php",
|
||
"yii2",
|
||
"laravel",
|
||
"symfony",
|
||
"django",
|
||
"nodejs",
|
||
"fastAPI",
|
||
"flask",
|
||
"python",
|
||
"exspress",
|
||
"adonis"
|
||
]
|
||
},
|
||
{
|
||
name: "Front",
|
||
skills: [
|
||
"react",
|
||
"next.js",
|
||
"typescript",
|
||
"redux",
|
||
"angular",
|
||
"vue",
|
||
"jquery",
|
||
"css (sass/scss, tailwind, bootstrap, БЭМ)"
|
||
]
|
||
}
|
||
];
|
||
|
||
const projects = [
|
||
{
|
||
description:
|
||
"Импортозамещение в управлении проектами <span>таск-трекер ITGuild</span>",
|
||
img: flag,
|
||
name: "flag"
|
||
},
|
||
{
|
||
description:
|
||
"<span>Работа Тудей</span> - это сервис, который специализируется на поиске работы на новых территориях Российской Федерации.",
|
||
img: rabota,
|
||
name: "rabota"
|
||
},
|
||
{
|
||
description:
|
||
"<span>Внедрение искусственного интеллекта</span> (ИИ) в IT-проекты. Интеграции любых популярных сервисов.",
|
||
img: hand,
|
||
name: "hand"
|
||
},
|
||
{
|
||
description:
|
||
"Новостной портал и удобный каталог компаний <span>DaInfo.pro</span> предоставляющих различные услуги и товары.",
|
||
img: fly,
|
||
name: "fly"
|
||
}
|
||
];
|
||
|
||
const steps = [
|
||
{
|
||
miniInfo: "Окунитесь в экосистему ITGUIL",
|
||
info: "<span>уточнение</span> деталей и <span>обсуждение</span> условий с менеджером ITGUILD"
|
||
},
|
||
{
|
||
miniInfo: "Окунитесь в экосистему ITGUIL",
|
||
info: "<span>подписание договора</span> без обязательств оплаты на данном этапе"
|
||
},
|
||
{
|
||
miniInfo: "Окунитесь в экосистему ITGUIL",
|
||
info: "<span>формирование</span> команды или подбор отдельных специалистов под требования клиентов"
|
||
},
|
||
{
|
||
miniInfo: "Окунитесь в экосистему ITGUIL",
|
||
info: "<span>интеграция специалистов</span> в команду клиента, ежедневная отчетность под контролем менеджера ITGUILD"
|
||
}
|
||
];
|
||
|
||
const [error, setError] = useState({
|
||
name: false,
|
||
companyName: false,
|
||
email: {
|
||
state: false,
|
||
text: ""
|
||
},
|
||
phoneNumber: {
|
||
state: false,
|
||
text: ""
|
||
},
|
||
requestDescription: false,
|
||
agreement: false
|
||
});
|
||
|
||
const [formData, setFormData] = useState({
|
||
name: "",
|
||
companyName: "",
|
||
email: "",
|
||
phoneNumber: "",
|
||
requestDescription: "",
|
||
file: null,
|
||
agreement: false
|
||
});
|
||
|
||
// Создаем ссылку на скрытый элемент input для файла
|
||
const hiddenFileInput = useRef(null);
|
||
|
||
// Программно нажимаем на скрытый элемент input для файла
|
||
// когда компонент Button будет нажат
|
||
const handleClick = () => {
|
||
hiddenFileInput.current.click();
|
||
};
|
||
|
||
// Вызываем функцию для обработки выбранного пользователем файла
|
||
const handleChangeFile = (event) => {
|
||
const fileUploaded = event.target.files[0];
|
||
setFormData((prevState) => ({
|
||
...prevState,
|
||
file: fileUploaded
|
||
}));
|
||
};
|
||
|
||
const handleChange = (e) => {
|
||
const { name, value, type, checked } = e.target;
|
||
setFormData((prevState) => ({
|
||
...prevState,
|
||
[name]: type === "checkbox" ? checked : value
|
||
}));
|
||
};
|
||
|
||
const handleSubmit = (e) => {
|
||
e.preventDefault();
|
||
|
||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||
const phoneRegex = /^\+?\d+$/;
|
||
|
||
const newError = {
|
||
name: !formData.name.trim(),
|
||
companyName: !formData.companyName.trim(),
|
||
email: {
|
||
state: !formData.email.trim() || !emailRegex.test(formData.email),
|
||
text: !formData.email.trim()
|
||
? "поле обязательно для заполнения"
|
||
: !emailRegex.test(formData.email)
|
||
? "e-mail не соответствует формату"
|
||
: ""
|
||
},
|
||
phoneNumber: {
|
||
state:
|
||
!formData.phoneNumber.trim() ||
|
||
!phoneRegex.test(formData.phoneNumber),
|
||
text: !formData.phoneNumber.trim()
|
||
? "поле обязательно для заполнения"
|
||
: !phoneRegex.test(formData.phoneNumber)
|
||
? "введите корректный номер телефона"
|
||
: ""
|
||
},
|
||
requestDescription: !formData.requestDescription.trim(),
|
||
agreement: !formData.agreement
|
||
};
|
||
|
||
setError(newError);
|
||
const isValid = Object.values(newError).every((err) => {
|
||
if (typeof err === "object") {
|
||
return !err.state;
|
||
}
|
||
return !err;
|
||
});
|
||
if (isValid) {
|
||
// Данные формы валидны, можно отправлять форму
|
||
console.log("Форма отправлена", formData);
|
||
setFormData({
|
||
name: "",
|
||
companyName: "",
|
||
email: "",
|
||
phoneNumber: "",
|
||
requestDescription: "",
|
||
file: null,
|
||
agreement: false
|
||
});
|
||
} else {
|
||
// Данные формы невалидны, показываем ошибки
|
||
console.log("Форма содержит ошибки", newError);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<section className="stack">
|
||
<AuthHeader />
|
||
<section className="stack__intro">
|
||
<div className="stack__container intro__container">
|
||
<div className="intro__info">
|
||
<span className="intro__suptitle">
|
||
Все еще пытаетесь
|
||
<br /> пасти котов?*
|
||
</span>
|
||
<h1 className="intro__title">
|
||
Аутстаф
|
||
<br />
|
||
финг
|
||
</h1>
|
||
<span className="intro__subtitle">IT-специалистов</span>
|
||
<p className="intro__about">
|
||
<span>Все для решения конкретных задач</span> в области
|
||
информационных технологий, обеспечивая оперативное усиление
|
||
команды и гибкое управление персоналом. <br />
|
||
<br />
|
||
Мы выделяем на проект как отдельных специалистов, так и целые
|
||
команды, в зависимости от потребностей и объема работы.
|
||
</p>
|
||
<div className="intro__links">
|
||
<button className="stack__button">оставить заявку</button>
|
||
<span className="intro__link">
|
||
Окунитесь в<br /> экосистему ITGUIL
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<SVG className="intro__ellipse" src={Ellipse} />
|
||
<div className="intro__aside">
|
||
<h3 className="aside__logo">ITGu ild</h3>
|
||
<div className="aside__clue">
|
||
<img src={clue} alt="clue" />
|
||
<p>
|
||
<span>Каждый день</span> база специалистов пополняется на{" "}
|
||
<span>+15 резюме</span>
|
||
</p>
|
||
</div>
|
||
<img className="aside__cat" src={cat} alt="cat" />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section className="stack__opportunity">
|
||
<img src={backgroundOpp} className="background__opportunity--left" />
|
||
<img src={backgroundOpp} className="background__opportunity--right" />
|
||
<div className="stack__container opportunity__container">
|
||
<img src={code} className="opportunity__code" />
|
||
<img src={code} className="opportunity__code--center" />
|
||
<div className="opportunity__block">
|
||
<h3 className="opportunity__title">Stack</h3>
|
||
<div className="opportunity__info">
|
||
<span className="info__subtitle">
|
||
Окунитесь в экосистему ITGUIL
|
||
</span>
|
||
<p className="info__about">
|
||
<span>Вы получаете полное управление над сотрудниками,</span>{" "}
|
||
имея возможность контролировать и заменять IT штат.
|
||
</p>
|
||
<div className="info__notification">
|
||
<img src={clue} alt="clue" />
|
||
<p>
|
||
Можем подготовить специалиста конкретно под ваш проект и
|
||
используемый стек. <br />
|
||
<span>
|
||
Таким образом вы сможете сэкономить ресурсы на поиск
|
||
кандидата.
|
||
</span>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="opportunity__subjects">
|
||
{subjects.map((subject) => {
|
||
return (
|
||
<div className="subject" key={subject.name}>
|
||
<h4>{subject.name}</h4>
|
||
<div className="subject__skills">
|
||
{subject.skills.map((skill) => {
|
||
return <span key={skill}>{skill}</span>;
|
||
})}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
<div className="opportunity__title-mobile">
|
||
<h3>Stack</h3>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section className="stack__projects projects">
|
||
<div className="stack__container projects__container">
|
||
<img className="projects__code" src={code} alt="code" />
|
||
<div className="projects__title">
|
||
<h3>ITGUILD</h3>
|
||
</div>
|
||
<div className="projects__block">
|
||
<h4>Наши проекты</h4>
|
||
<div className="projects__examples">
|
||
{projects.map((project, index) => {
|
||
return (
|
||
<div key={index} className="stack__project">
|
||
<span className="project__img">
|
||
<img
|
||
className={project.name}
|
||
src={project.img}
|
||
alt="img"
|
||
/>
|
||
</span>
|
||
<p
|
||
dangerouslySetInnerHTML={{ __html: project.description }}
|
||
></p>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
<div className="projects__info">
|
||
<p>
|
||
<span>Мы обеспечиваем</span> финансовые, юридические и кадровые
|
||
гарантии, предоставляем SLA и берем на себя ответственность за
|
||
работу команды. <br /> <br /> Вам не требуется заниматься
|
||
поиском, оформлением или увольнением сотрудников —{" "}
|
||
<span>мы берем на себя все хлопоты.</span>
|
||
</p>
|
||
<button>оставить заявку</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section className="stack__steps">
|
||
<div className="stack__container steps__container">
|
||
<div className="steps__head">
|
||
<h4>как это работает?</h4>
|
||
<div className="steps__info">
|
||
<p>
|
||
Аутстаффинг представляет собой специфическую модель найма
|
||
персонала, отличающуюся от аутсорсинга.
|
||
</p>
|
||
<p>
|
||
<span>
|
||
В контексте аутстаффинга вы нанимаете специалистов в области
|
||
ИТ,
|
||
</span>{" "}
|
||
оплачивая их по их конкретным навыкам, и берете на себя
|
||
организацию их работы.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
<div className="steps__items">
|
||
{steps.map((step, index) => {
|
||
return (
|
||
<div key={index} className="item__wrapper">
|
||
<div className="item__head">
|
||
<h4>{`${index + 1}.`}</h4>
|
||
<p>{step.miniInfo}</p>
|
||
</div>
|
||
<div className="steps__item" key={index}>
|
||
<p
|
||
className="item__info"
|
||
dangerouslySetInnerHTML={{ __html: step.info }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
<div className="steps__portfolio">
|
||
<img src={portfolio} alt="portfolio" />
|
||
</div>
|
||
<div className="steps__portfolio-mobile">
|
||
<img src={bagITguild} alt="portfolio" />
|
||
</div>
|
||
<img
|
||
className="steps__code steps__code--first"
|
||
src={code}
|
||
alt="code"
|
||
/>
|
||
<img
|
||
className="steps__code steps__code--second"
|
||
src={code}
|
||
alt="code"
|
||
/>
|
||
</div>
|
||
<img src={backgroundOpp} className="steps__background" />
|
||
</section>
|
||
<section className="stack__reviews">
|
||
<div className="stack__container reviews__container">
|
||
<div className="reviews__info">
|
||
<div className="reviews__info-counter">375</div>
|
||
<span>Довольных клиентов</span>
|
||
<p>
|
||
Предоставляем на аутстаффинг frontend- и backend - разработчиков
|
||
уровня от junior до middle+
|
||
</p>
|
||
<p>
|
||
Можем сделать оценку проекта, ревью кода, составить коммерческое
|
||
предложение, рекомендации касаемо стека технологий и организации
|
||
архитектуры разрабатываемого проекта.
|
||
</p>
|
||
</div>
|
||
|
||
<div className="reviews__content">
|
||
<h4>Что о нас говорят</h4>
|
||
<div className="reviews__content-container">
|
||
<div className="review">
|
||
<div className="review__client">
|
||
<img src={reviewsImgMok} alt="reviewsImgMok" />
|
||
<span>Александр Гузеев</span>
|
||
<p>Руководитель проекта ООО “ЭЛАР”</p>
|
||
</div>
|
||
|
||
<div className="review__comment">
|
||
<p>
|
||
Команда ITGUILD берется за решение широкого круга задач, не
|
||
боясь при этом ни сжатых сроков, ни сложной специфики
|
||
проектов, и успешно доводит их ло решения. <br />
|
||
<br />
|
||
Разаработчики Кирилла смогли не только усилить существующую
|
||
команду разработки, став ее полноценной частью, но и
|
||
привести в проект новые идеи, свои знания и опыт.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="reviews__content-buttons">
|
||
<button>
|
||
<img src={arrowReviewsLeft} alt="" />
|
||
</button>
|
||
<button>
|
||
<img src={arrowReviewsRight} alt="" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<img
|
||
className="reviews__code reviews__code--first"
|
||
src={codeWhite}
|
||
alt="code"
|
||
/>
|
||
<img
|
||
className="reviews__code reviews__code--second"
|
||
src={codeWhite}
|
||
alt="code"
|
||
/>
|
||
</div>
|
||
</section>
|
||
|
||
<section className="stack__contact">
|
||
<img src={backgroundOpp} className="contact__background" />
|
||
<img src={backgroundOpp} className="contact__background--right" />
|
||
<div className="stack__container contact__container">
|
||
<img src={code} className="contact__code" />
|
||
<img src={code} className="contact__code--center" />
|
||
<div className="contact__block">
|
||
<div className="contact-info">
|
||
<img src={clue} alt="clue" />
|
||
<h3 className="info__title">ПОИСК</h3>
|
||
<div className="info-content">
|
||
<div className="info-content__title">
|
||
<p>
|
||
<span>Заполните, пожалуйста, форму</span> и <br /> наш
|
||
менеджер обязательно с вами свяжется
|
||
</p>
|
||
<h4>Нужен специалист?</h4>
|
||
</div>
|
||
<div className="info-content__nav">
|
||
<span>Посмотрите и другие продукты ITGUILD</span>
|
||
<div className="info-content__buttons">
|
||
<button>Система для отчётности</button>
|
||
<button>Система контроля версий GIT</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<form className="contact__form" onSubmit={handleSubmit}>
|
||
<span className="form__subtitle">
|
||
Окунитесь в экосистему ITGUIL
|
||
</span>
|
||
<div className="form-container">
|
||
<p>
|
||
Пожалуйста, заполните форму и наш менеджер обязательно с вами
|
||
свяжется
|
||
</p>
|
||
<div className="form-info">
|
||
<div>
|
||
<input
|
||
className={error.name ? "input-error" : ""}
|
||
type="text"
|
||
name="name"
|
||
value={formData.name}
|
||
onChange={handleChange}
|
||
placeholder="имя и фамилия"
|
||
/>
|
||
{error.name && (
|
||
<span className="error">
|
||
поле обязательно для заполнения
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div>
|
||
<input
|
||
className={error.companyName ? "input-error" : ""}
|
||
type="text"
|
||
name="companyName"
|
||
value={formData.companyName}
|
||
onChange={handleChange}
|
||
placeholder="название вашей компании"
|
||
/>
|
||
{error.companyName && (
|
||
<span className="error">
|
||
поле обязательно для заполнения
|
||
</span>
|
||
)}
|
||
</div>
|
||
<div>
|
||
<input
|
||
className={error.email.state ? "input-error" : ""}
|
||
type="text"
|
||
name="email"
|
||
value={formData.email}
|
||
onChange={handleChange}
|
||
placeholder="e-mail"
|
||
/>
|
||
{error.email.state && (
|
||
<span className="error">{error.email.text}</span>
|
||
)}
|
||
</div>
|
||
<div>
|
||
<input
|
||
className={error.phoneNumber.state ? "input-error" : ""}
|
||
type="tel"
|
||
name="phoneNumber"
|
||
value={formData.phoneNumber}
|
||
onChange={handleChange}
|
||
placeholder="+7"
|
||
/>
|
||
{error.phoneNumber.state && (
|
||
<span className="error">{error.phoneNumber.text}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="form-description">
|
||
<textarea
|
||
className={error.requestDescription ? "input-error" : ""}
|
||
name="requestDescription"
|
||
value={formData.requestDescription}
|
||
onChange={handleChange}
|
||
placeholder="опишите, что вам требуется"
|
||
/>
|
||
{error.requestDescription && (
|
||
<span className="error">
|
||
поле обязательно для заполнения
|
||
</span>
|
||
)}
|
||
</div>
|
||
|
||
<div className="form-file">
|
||
<div className="button-upload" onClick={handleClick}>
|
||
<img src={fileUploadIcon} alt="" />
|
||
<span>прикрепить файл</span>
|
||
</div>
|
||
<input
|
||
type="file"
|
||
name="file"
|
||
onChange={handleChangeFile}
|
||
ref={hiddenFileInput}
|
||
style={{ display: "none" }}
|
||
/>
|
||
<p>
|
||
.eps, .ai, .psd, .jpg, .png, .pdf, .doc, .docx, .xlsx, .xls,
|
||
.ppt, .jpeg <br />
|
||
<span>максимальный размер одного файла 10 Мб</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div className="form-submit">
|
||
<div className="form-submit__agreement">
|
||
<input
|
||
type="radio"
|
||
name="agreement"
|
||
checked={formData.agreement}
|
||
onChange={handleChange}
|
||
/>
|
||
<p>
|
||
Соглашаюсь с <a href="">Пользовательским соглашением</a> и
|
||
<br />
|
||
<a href="">Политикой обработки данных</a>
|
||
</p>
|
||
</div>
|
||
|
||
<button className="form-submit__button" type="submit">
|
||
оставить заявку
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
<Footer />
|
||
</section>
|
||
</section>
|
||
);
|
||
};
|