RC-7: create home page components
This commit is contained in:
@@ -1,7 +1,67 @@
|
||||
import { memo } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import AboutUs from './components/AboutUs';
|
||||
import ArticleCardsList from './components/ArticleCardsList';
|
||||
import FeedbackForm from './components/FeedbackForm';
|
||||
import Statistics from './components/Statistics';
|
||||
import Welcome from './components/Welcome';
|
||||
|
||||
function Home(props) {
|
||||
return <div>Heeeeelloooooo!</div>;
|
||||
const articleCardsMock = [
|
||||
{
|
||||
id: '123',
|
||||
title: 'Lorem ipsum dolor sit amet ornare amet consequat ultricies auctor.',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus. Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placmeget rhoncus ut settrnhg ips...',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1664575602276-acd073f104c1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
||||
updated: '12.12.2022',
|
||||
},
|
||||
{
|
||||
id: '234',
|
||||
title: 'Lorem ipsum dolor sit amet ornare amet consequat us auctoit amet orare ametr...',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncusnon eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus.',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1664575602276-acd073f104c1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
||||
updated: '12.12.2022',
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
title: 'Lorem ipsum dolor sit amet ornare amet consequat ultricies auctor.',
|
||||
description:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus. Lorem ipsum dolor sit amet consectetur. Quames lorem id luctus viverra ligula placerat mus.',
|
||||
image:
|
||||
'https://images.unsplash.com/photo-1664575602276-acd073f104c1?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
||||
updated: '12.12.2022',
|
||||
},
|
||||
];
|
||||
|
||||
function Home({ t }) {
|
||||
const [servicesCards, setServicesCards] = useState([]);
|
||||
const [blogCards, setBlogCards] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
setServicesCards(articleCardsMock);
|
||||
setBlogCards(articleCardsMock);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-full bg-primary-main">
|
||||
<Welcome />
|
||||
<div className="max-w-8xl w-full px-10 mx-auto">
|
||||
<Statistics />
|
||||
<AboutUs />
|
||||
<ArticleCardsList title={t('services_title')} cards={servicesCards} className="mb-20" />
|
||||
<ArticleCardsList
|
||||
title={t('blog_title')}
|
||||
cards={blogCards}
|
||||
id="blog"
|
||||
className="pt-72 mb-28"
|
||||
/>
|
||||
<FeedbackForm />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Home);
|
||||
export default withTranslation('homePage')(Home);
|
||||
|
||||
37
src/app/main/home/components/AboutUs.js
Normal file
37
src/app/main/home/components/AboutUs.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { memo } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function AboutUs({ t }) {
|
||||
return (
|
||||
<section id="about-us" className="flex flex-col items-center pt-72 mb-80">
|
||||
<h2 className="self-start mb-56 text-[48px] font-semibold">{t('about_us_title')}</h2>
|
||||
<div className="flex gap-64 mb-[126px]">
|
||||
<div className="flex items-center">
|
||||
<iframe
|
||||
className="rounded-[20px]"
|
||||
width="715"
|
||||
height="402"
|
||||
src="https://www.youtube.com/embed/rNSIwjmynYQ?controls=0"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowFullScreen
|
||||
/>
|
||||
</div>
|
||||
<aside className="flex flex-col items-center py-40 px-52 bg-primary-light rounded-[20px]">
|
||||
<h3 className="mb-16 text-lg text-common-layout font-medium">{t('about_us_subject')}</h3>
|
||||
<p className="mb-16 text-lg text-common-layout font-light">{t('about_us_text_1')}</p>
|
||||
<p className="mb-16 text-lg text-common-layout font-light">{t('about_us_text_2')}</p>
|
||||
</aside>
|
||||
</div>
|
||||
<Link
|
||||
className="w-[220px] py-[17px] text-center text-base text-primary-light font-semibold tracking-widest uppercase rounded-2xl bg-secondary-light shadow hover:shadow-hover hover:shadow-secondary-light ease-in-out duration-300"
|
||||
to="/rent-and-buy"
|
||||
>
|
||||
{t('research_btn')}
|
||||
</Link>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTranslation('homePage')(memo(AboutUs));
|
||||
33
src/app/main/home/components/ArticleCard.js
Normal file
33
src/app/main/home/components/ArticleCard.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import { memo } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function ArticleCard({ t, id, title, description, image, updated }) {
|
||||
return (
|
||||
<article className="flex flex-col justify-between max-w-[460px] w-full h-[526px] bg-primary-light rounded-[20px] shadow-light">
|
||||
<div>
|
||||
<img
|
||||
className="w-full h-[230px] mb-20 rounded-[20px] object-cover"
|
||||
src={image}
|
||||
alt={title}
|
||||
width="460"
|
||||
height="230"
|
||||
loading="lazy"
|
||||
/>
|
||||
<h3 className="px-[15px] mb-20 text-xl leading-light font-medium">{title}</h3>
|
||||
<p className="px-[15px] text-lg text-common-layout font-light">{description}</p>
|
||||
</div>
|
||||
<div className="px-[15px] mb-[17px] flex justify-between items-center">
|
||||
<p className="text-lg text-common-secondary font-light">{updated}</p>
|
||||
<Link
|
||||
className="flex items-center justify-center w-[60px] h-[60px] text-secondary-main rounded-full border-2 border-secondary-main"
|
||||
to={`/blog/${id}`}
|
||||
>
|
||||
{t('article_btn')}
|
||||
</Link>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTranslation('homePage')(memo(ArticleCard));
|
||||
17
src/app/main/home/components/ArticleCardsList.js
Normal file
17
src/app/main/home/components/ArticleCardsList.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import { memo } from 'react';
|
||||
import ArticleCard from './ArticleCard';
|
||||
|
||||
function ArticleCardsList({ title, cards, ...attrs }) {
|
||||
return (
|
||||
<section {...attrs}>
|
||||
<h2 className="mb-56 text-[48px] font-semibold">{title}</h2>
|
||||
<div className="flex gap-20">
|
||||
{cards.map((card) => (
|
||||
<ArticleCard {...card} key={card.id} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(ArticleCardsList);
|
||||
157
src/app/main/home/components/FeedbackForm.js
Normal file
157
src/app/main/home/components/FeedbackForm.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import _ from '@lodash';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import { forwardRef, memo, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import * as yup from 'yup';
|
||||
|
||||
const defaultValues = {
|
||||
name: '',
|
||||
email: '',
|
||||
message: '',
|
||||
};
|
||||
|
||||
const StyledTextField = forwardRef((props, ref) => (
|
||||
<TextField
|
||||
InputProps={{
|
||||
sx: {
|
||||
background: (theme) => theme.palette.background.paper,
|
||||
borderRadius: '10px',
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
));
|
||||
|
||||
function FeedbackForm({ t }) {
|
||||
const [isFormSubmitted, setIsFormSubmitted] = useState(false);
|
||||
|
||||
const schema = yup.object().shape({
|
||||
name: yup.string().required(t('feedback_required')),
|
||||
email: yup.string().email(t('feedback_email_error')).required(t('feedback_required')),
|
||||
message: yup
|
||||
.string()
|
||||
.required(t('feedback_required'))
|
||||
.min(5, t('min_length_error', { length: 5 }))
|
||||
.max(255, t('max_length_error', { length: 255 })),
|
||||
});
|
||||
|
||||
const { control, formState, handleSubmit, setError } = useForm({
|
||||
mode: 'onChange',
|
||||
defaultValues,
|
||||
resolver: yupResolver(schema),
|
||||
});
|
||||
|
||||
const { isValid, dirtyFields, errors } = formState;
|
||||
|
||||
function onSubmit() {
|
||||
// setError('root', 'error');
|
||||
setIsFormSubmitted(true);
|
||||
}
|
||||
|
||||
return (
|
||||
<section id="contacts" className="pt-72 mb-88">
|
||||
{!isFormSubmitted && (
|
||||
<form
|
||||
name="signinForm"
|
||||
noValidate
|
||||
className="grid grid-cols-2 gap-x-20 gap-y-32 px-80 py-40 bg-primary-light rounded-[20px]"
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<legend className="col-span-2 justify-self-center max-w-[860px] mb-8 text-4xl font-medium text-center">
|
||||
{t('feedback_title')}
|
||||
</legend>
|
||||
|
||||
<Controller
|
||||
name="name"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledTextField
|
||||
{...field}
|
||||
label={t('feedback_name')}
|
||||
type="text"
|
||||
error={!!errors.name}
|
||||
helperText={errors?.name?.message}
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="email"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<StyledTextField
|
||||
{...field}
|
||||
label={t('feedback_email')}
|
||||
type="email"
|
||||
error={!!errors.email}
|
||||
helperText={errors?.email?.message}
|
||||
variant="outlined"
|
||||
required
|
||||
fullWidth
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="message"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<TextField
|
||||
{...field}
|
||||
className="col-span-2 mb-8"
|
||||
label={t('feedback_message')}
|
||||
type="text"
|
||||
error={!!errors.message}
|
||||
helperText={errors?.message?.message}
|
||||
variant="outlined"
|
||||
required
|
||||
multiline
|
||||
rows={5}
|
||||
fullWidth
|
||||
InputProps={{
|
||||
sx: {
|
||||
padding: 0,
|
||||
background: (theme) => theme.palette.background.paper,
|
||||
borderRadius: '10px',
|
||||
},
|
||||
}}
|
||||
// eslint-disable-next-line react/jsx-no-duplicate-props
|
||||
inputProps={{
|
||||
sx: {
|
||||
padding: '16.5px 14px',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="col-span-2 justify-self-center flex flex-col items-center justify-center gap-10 w-full">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
className="w-[220px] text-base uppercase rounded-xl"
|
||||
aria-label={t('feedback_btn')}
|
||||
disabled={_.isEmpty(dirtyFields) || !isValid}
|
||||
type="submit"
|
||||
size="large"
|
||||
>
|
||||
{t('feedback_btn')}
|
||||
</Button>
|
||||
{errors.root?.message && (
|
||||
<p className="text-l text-error-main">{errors.root?.message}</p>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTranslation('homePage')(memo(FeedbackForm));
|
||||
18
src/app/main/home/components/Statistics.js
Normal file
18
src/app/main/home/components/Statistics.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import { memo } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import StatisticsCard from './StatisticsCard';
|
||||
|
||||
function Statistics({ t }) {
|
||||
return (
|
||||
<section className="flex flex-col items-center">
|
||||
<div className="flex gap-20 mb-[136px]">
|
||||
<StatisticsCard title={t('stat_title_1')} text={t('stat_text_1')} />
|
||||
<StatisticsCard title={t('stat_title_2')} text={t('stat_text_2')} />
|
||||
<StatisticsCard title={t('stat_title_3')} text={t('stat_text_3')} />
|
||||
</div>
|
||||
<h2 className="max-w-[1020px] mb-4 text-5xl text-center">{t('caption')}</h2>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTranslation('homePage')(memo(Statistics));
|
||||
12
src/app/main/home/components/StatisticsCard.js
Normal file
12
src/app/main/home/components/StatisticsCard.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { memo } from 'react';
|
||||
|
||||
function StatisticsCard({ title, text }) {
|
||||
return (
|
||||
<article className="flex flex-col justify-start items-center max-w-[460px] w-full min-h-[356px] h-full pt-32 px-40 text-common-primary bg-primary-light rounded-[20px] shadow-light even:bg-secondary-main even:text-primary-light">
|
||||
<h3 className="mb-52 text-[80px] font-semibold">{title}</h3>
|
||||
<p className="text-xl leading-5 font-light">{text}</p>
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(StatisticsCard);
|
||||
41
src/app/main/home/components/Welcome.js
Normal file
41
src/app/main/home/components/Welcome.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import { memo, useState } from 'react';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import SearchInput from '../../shared-components/SearchInput';
|
||||
|
||||
function Welcome({ t }) {
|
||||
const [query, setQuery] = useState('');
|
||||
|
||||
const onInputType = (event) => {
|
||||
const { target } = event;
|
||||
const value = target?.value ?? '';
|
||||
console.log(value);
|
||||
setQuery(value);
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
// query
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="h-[calc(100vh-72px)] mb-[105px] bg-home-welcome bg-cover">
|
||||
<div className="flex flex-col max-w-8xl w-full mx-auto pt-160 px-10">
|
||||
<h1 className="max-w-[910px] mb-[30px] text-7xl font-bold text-primary-light">
|
||||
{t('title')}
|
||||
</h1>
|
||||
<p className="max-w-[820px] mb-[100px] text-2xl font-medium text-primary-light">
|
||||
{t('subtitle')}
|
||||
</p>
|
||||
<SearchInput
|
||||
mode="simple"
|
||||
placeholder={t('main_input_placeholder')}
|
||||
btnText={t('main_input_btn')}
|
||||
query={query}
|
||||
onType={onInputType}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default withTranslation('homePage')(memo(Welcome));
|
||||
@@ -1,3 +1,41 @@
|
||||
const locale = {};
|
||||
const locale = {
|
||||
title: 'Lorem ipsum dolor sit amet ornare amet consequat ultricies auctor.',
|
||||
subtitle:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus.',
|
||||
main_input_placeholder: 'Lorem ipsum dolor sit amemolestie non eget rhoncus ut sed.',
|
||||
main_input_btn: 'calculate',
|
||||
stat_title_1: '158',
|
||||
stat_text_1:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus.',
|
||||
stat_title_2: '89%',
|
||||
stat_text_2:
|
||||
'Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lom id luctus viverra ligula placerat mus.',
|
||||
stat_title_3: '10k+',
|
||||
stat_text_3:
|
||||
'Egestas scelerisque fames lorem. Lorem ipsum dolor sit amet consectetur. Quam molestie non eget rhoncus ut sed. Egestas scelerisque fames lorem id luctus viverra ligula placerat mus.',
|
||||
caption:
|
||||
'Lorem ipsum dolor sit amet ornare amet consequat ultricies sit amet ornare amet consequat ultricie auctor.',
|
||||
about_us_title: 'About us',
|
||||
about_us_watch: 'Watch Demo',
|
||||
about_us_subject:
|
||||
'Lorem ipsum dolor sit amet consectetur. Eu amet tellus tristique viverra accumsan ac vel eu. Ut morbi tempor quam elit orci nulla mattis. Vitae vitae egestas amet at gravida montes sit. Eu sit at sapien enim platea eget arcu. Sed consectetur sit in aliquam mi tellus at scelerisque. Tempor lacus sit ut augue amet penatibus amet malesuada orci.',
|
||||
about_us_text_1:
|
||||
'Lorevinar adipiscing tempus interdum lobortis. Mauris porta sagittis sed tempor tra urna. Volutpat dui nisl lorem gravida enim ut habitant sit. Natoque viverra habitasse tincidunt tristique sit.',
|
||||
about_us_text_2:
|
||||
'Vel phasellus pellentesque duis lorem maecenas. Vestibulum dui massa elit suspendisse porttitor integer praesent. Aliquam massa ante vestibulum neque sed imperdiet rhoncus. Turpis quam sed nibh id ultricies. Mattis mattis sit enim nunc interdum adipiscing. Arcu maecenas quis eget eget nunc quam. Id et quis enim morbi magnis. Nam ut habitasse sagittis magna morbi augue at.',
|
||||
research_btn: 'research',
|
||||
services_title: 'Services',
|
||||
blog_title: 'Blog',
|
||||
article_btn: 'See',
|
||||
feedback_title: 'Lorem ipsum dolor sit amet ornare amet consequat us auctoit amet orare ametr...',
|
||||
feedback_name: 'Name',
|
||||
feedback_email: 'Email',
|
||||
feedback_email_error: 'incorrect email',
|
||||
feedback_message: 'Message',
|
||||
feedback_btn: 'send',
|
||||
feedback_required: 'this field is required',
|
||||
max_length_error: 'The maximum length is {{length}}',
|
||||
min_length_error: 'The minimum length is {{length}}',
|
||||
};
|
||||
|
||||
export default locale;
|
||||
|
||||
BIN
src/assets/images/welcome-background.webp
Normal file
BIN
src/assets/images/welcome-background.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
Reference in New Issue
Block a user