RC-7: create home page components

This commit is contained in:
2023-08-06 15:28:27 +01:00
parent 120ad1b4f7
commit 2160d206e9
10 changed files with 418 additions and 5 deletions

View File

@@ -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);

View 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));

View 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));

View 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);

View 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));

View 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));

View 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);

View 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));

View File

@@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB