RC-10: create shared components for history and favorites pages

This commit is contained in:
2023-08-27 11:56:36 +01:00
parent e6912e2541
commit a5b30367cb
15 changed files with 467 additions and 149 deletions

View File

@@ -9,7 +9,7 @@ function AboutUs({ t }) {
<div className="flex gap-64 mb-[126px]"> <div className="flex gap-64 mb-[126px]">
<div className="flex items-center"> <div className="flex items-center">
<iframe <iframe
className="rounded-[20px]" className="rounded-20"
width="715" width="715"
height="402" height="402"
src="https://www.youtube.com/embed/rNSIwjmynYQ?controls=0" src="https://www.youtube.com/embed/rNSIwjmynYQ?controls=0"
@@ -18,7 +18,7 @@ function AboutUs({ t }) {
allowFullScreen allowFullScreen
/> />
</div> </div>
<aside className="flex flex-col items-center py-40 px-52 bg-primary-light rounded-[20px]"> <aside className="flex flex-col items-center py-40 px-52 bg-primary-light rounded-20">
<h3 className="mb-16 text-lg text-common-layout font-medium">{t('about_us_subject')}</h3> <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_1')}</p>
<p className="mb-16 text-lg text-common-layout font-light">{t('about_us_text_2')}</p> <p className="mb-16 text-lg text-common-layout font-light">{t('about_us_text_2')}</p>

View File

@@ -4,10 +4,10 @@ import { Link } from 'react-router-dom';
function ArticleCard({ t, id, title, description, image, updated }) { function ArticleCard({ t, id, title, description, image, updated }) {
return ( return (
<article className="flex flex-col justify-between max-w-[460px] w-full h-[526px] bg-primary-light rounded-[20px] shadow-light"> <article className="flex flex-col justify-between max-w-[460px] w-full h-[526px] bg-primary-light rounded-20 shadow-light">
<div> <div>
<img <img
className="w-full h-[230px] mb-20 rounded-[20px] object-cover" className="w-full h-[230px] mb-20 rounded-20 object-cover"
src={image} src={image}
alt={title} alt={title}
width="460" width="460"

View File

@@ -58,7 +58,7 @@ function FeedbackForm({ t }) {
<form <form
name="signinForm" name="signinForm"
noValidate noValidate
className="grid grid-cols-2 gap-x-20 gap-y-32 px-80 py-40 bg-primary-light rounded-[20px]" className="grid grid-cols-2 gap-x-20 gap-y-32 px-80 py-40 bg-primary-light rounded-20"
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
> >
<legend className="col-span-2 justify-self-center max-w-[860px] mb-8 text-4xl font-medium text-center"> <legend className="col-span-2 justify-self-center max-w-[860px] mb-8 text-4xl font-medium text-center">

View File

@@ -2,7 +2,7 @@ import { memo } from 'react';
function StatisticsCard({ title, text }) { function StatisticsCard({ title, text }) {
return ( 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"> <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-20 shadow-light even:bg-secondary-main even:text-primary-light">
<h3 className="mb-52 text-[80px] font-semibold">{title}</h3> <h3 className="mb-52 text-[80px] font-semibold">{title}</h3>
<p className="text-xl leading-5 font-light">{text}</p> <p className="text-xl leading-5 font-light">{text}</p>
</article> </article>

View File

@@ -104,6 +104,7 @@ function ProfilePage({ t }) {
}); });
const { dirtyFields, errors } = formState; const { dirtyFields, errors } = formState;
// eslint-disable-next-line consistent-return
const uploadPicture = async (event) => { const uploadPicture = async (event) => {
const { target } = event; const { target } = event;
if (target.files && target.files[0]) { if (target.files && target.files[0]) {

View File

@@ -0,0 +1,17 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import Typography from '@mui/material/Typography';
import clsx from 'clsx';
import { memo } from 'react';
function DateMark({ className, update }) {
return (
<span className={clsx('flex justify-center items-center gap-10', className)}>
<FuseSvgIcon>heroicons-outline:calendar</FuseSvgIcon>
<Typography variant="body1" className="text-lg font-medium text-common-secondary">
{update}
</Typography>
</span>
);
}
export default memo(DateMark);

View File

@@ -0,0 +1,21 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import clsx from 'clsx';
import { memo } from 'react';
function UpdateMark({ className, favorite, id, onClick }) {
const hasCallback = typeof onClick !== 'undefined';
return (
<button
className="w-[24px] h-[24px] cursor-pointer"
type="button"
onClick={() => hasCallback && onClick(id)}
>
<FuseSvgIcon className={clsx('w-full h-full', className, favorite && 'text-secondary-main')}>
heroicons-outline:heart
</FuseSvgIcon>
</button>
);
}
export default memo(UpdateMark);

View File

@@ -0,0 +1,25 @@
import Typography from '@mui/material/Typography';
import _ from '@lodash';
import clsx from 'clsx';
import { memo, useMemo } from 'react';
function MetaMark({ className, category, status }) {
const text = useMemo(
() => (status ? `Status: ${_.startCase(status)}` : _.startCase(category)),
[category, status]
);
return (
<Typography
variant="body1"
className={clsx(
'flex justify-center align-center px-20 py-2 font-medium border-2 rounded-8',
className
)}
>
{text}
</Typography>
);
}
export default memo(MetaMark);

View File

@@ -0,0 +1,56 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import Typography from '@mui/material/Typography';
import _ from '@lodash';
import clsx from 'clsx';
import { memo } from 'react';
function PropertiesHeader({ className, categories, layouts, onCategory, onLayout }) {
return (
<div
className={clsx(
'flex items-center gap-44 w-full py-9 px-52 rounded-20 bg-white shadow-light',
className
)}
>
<div className="grow flex items-center justify-start gap-16 py-16 border-r-1 border-common-disabled">
{categories.map(({ name, amount, active }, idx) => (
<button
key={name + idx}
type="button"
className={clsx(
'text-2xl text-common-layout cursor-pointer',
active && 'text-secondary-main font-semibold cursor-default'
)}
onClick={() => onCategory(name)}
>{`${_.startCase(name)} (${amount})`}</button>
))}
</div>
<div className="flex items-center gap-60 py-16">
{layouts.map(({ name, active }, idx) => (
<button
key={name + idx}
type="button"
className={clsx(
'flex justify-center items-center gap-10 cursor-pointer',
active && '!cursor-default'
)}
onClick={() => onLayout(name)}
>
<FuseSvgIcon className={clsx('text-common-secondary', active && 'text-secondary-main')}>
{`heroicons-outline:view-${name}`}
</FuseSvgIcon>
<Typography
variant="body1"
className={clsx('text-2xl text-common-secondary', active && 'text-secondary-main')}
>
{_.startCase(name)}
</Typography>
</button>
))}
</div>
</div>
);
}
export default memo(PropertiesHeader);

View File

@@ -0,0 +1,77 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import Typography from '@mui/material/Typography';
import { memo } from 'react';
import { Link } from 'react-router-dom';
import DateMark from './DateMark';
import FavoriteButton from './FavoriteButton';
import MetaMark from './MetaMark';
import StatisticsValue from './StatisticsValue';
function PropertyGridCard({
id,
image,
title,
category,
status,
update,
favorite,
statistics,
onFavorite,
onDelete,
}) {
return (
<article className="w-[470px] px-20 pt-20 rounded-20 bg-white shadow-light overflow-hidden">
<div className="flex justify-between mb-[25px]">
<div className="flex gap-10">
<MetaMark
category={category}
className="text-common-highlight1 border-common-highlight1"
/>
<MetaMark status={status} className="text-common-highlight2 border-common-highlight2" />
</div>
<div className="flex gap-20">
<FavoriteButton favorite={favorite} id={id} onClick={onFavorite} />
<DateMark update={update} />
</div>
</div>
<div className="flex flex-col items-start mb-[29px]">
<Typography variant="h3" className="mb-[17px] text-3xl font-semibold">
{title}
</Typography>
<img src={image} alt={title} className="w-full h-160 rounded-3xl object-cover" />
<div className="grid grid-cols-2 justify-between w-full">
{statistics.map(({ subject, value, mode }, idx) => (
<StatisticsValue
key={subject + value + idx}
subject={subject}
value={value}
mode={mode}
/>
))}
</div>
</div>
<div className="flex w-[calc(100%+40px)] -mx-20 border-t-1 border-common-disabled">
<button
className="flex justify-center items-center gap-10 w-full py-20 border-r-1 border-common-disabled cursor-pointer"
type="button"
onClick={() => onDelete(id)}
>
<FuseSvgIcon className="text-common-secondary">heroicons-outline:trash</FuseSvgIcon>
<Typography variant="body1" className="text-common-secondary font-medium">
Delete
</Typography>
</button>
<Link
className="flex justify-center items-center w-full py-[22px] text-lg font-semibold text-secondary-main border-l-1 border-white cursor-pointer"
to={`/property/${id}`}
>
Details
</Link>
</div>
</article>
);
}
export default memo(PropertyGridCard);

View File

@@ -0,0 +1,83 @@
import FuseSvgIcon from '@fuse/core/FuseSvgIcon';
import Typography from '@mui/material/Typography';
import { memo } from 'react';
import { Link } from 'react-router-dom';
import DateMark from './DateMark';
import FavoriteButton from './FavoriteButton';
import MetaMark from './MetaMark';
import StatisticsValue from './StatisticsValue';
function PropertyListItem({
id,
image,
title,
category,
status,
update,
favorite,
statistics,
onFavorite,
onDelete,
}) {
return (
<article className="flex w-full p-20 rounded-20 bg-white shadow-light overflow-hidden">
<img
src={image}
alt={title}
className="w-[80px] h-[80px] mr-[15px] rounded-3xl object-cover"
/>
<div className="mr-20">
<div className="flex justify-start gap-60 mb-[22px]">
<div className="flex gap-10">
<MetaMark
category={category}
className="text-common-highlight1 border-common-highlight1"
/>
<MetaMark status={status} className="text-common-highlight2 border-common-highlight2" />
</div>
<div className="flex gap-20">
<FavoriteButton favorite={favorite} id={id} onClick={onFavorite} />
<DateMark update={update} />
</div>
</div>
<Typography variant="h3" className="max-w-[480px] text-3xl font-semibold truncate">
{title}
</Typography>
</div>
<div className="grow flex mr-20">
{statistics.map(
({ subject, value, mode }, idx) =>
(idx === 0 || idx === 1) && (
<StatisticsValue
key={subject + value + idx}
subject={subject}
value={value}
mode={mode}
className="-my-[6px]"
/>
)
)}
</div>
<div className="flex flex-col justify-between gap-16">
<button
className="self-end flex justify-center items-center gap-10 cursor-pointer"
type="button"
onClick={() => onDelete(id)}
>
<FuseSvgIcon className="text-common-secondary">heroicons-outline:trash</FuseSvgIcon>
</button>
<Link
className="flex justify-center items-center px-[53px] py-20 -mr-20 -mb-20 text-lg font-semibold text-secondary-main border-l-1 border-t-1 rounded-tl-[20px] border-common-disabled cursor-pointer"
to={`/property/${id}`}
>
Details
</Link>
</div>
</article>
);
}
export default memo(PropertyListItem);

View File

@@ -0,0 +1,38 @@
import Typography from '@mui/material/Typography';
import { STATISTICS_MODES } from 'app/configs/consts';
import clsx from 'clsx';
import { memo } from 'react';
function StatisticsValue({ className, subject, value, mode }) {
const isPositive = mode === STATISTICS_MODES.positive;
const isExtraPositive = mode === STATISTICS_MODES.extra_positive;
const isNegative = mode === STATISTICS_MODES.negative;
const isExtraNegative = mode === STATISTICS_MODES.extra_negative;
return (
<span
className={clsx(
'max-w-[210px] w-full py-[15px] pl-20 text-left rounded-xl',
className,
isExtraPositive && 'bg-accept-light',
isExtraNegative && 'bg-error-light'
)}
>
<Typography variant="body1" className="text-lg text-left leading-tight">
{subject}
</Typography>
<Typography
variant="h4"
className={clsx(
'text-[28px] font-semibold text-left leading-tight',
(isPositive || isExtraPositive) && 'text-accept-main',
(isNegative || isExtraNegative) && 'text-error-main'
)}
>
{value}
</Typography>
</span>
);
}
export default memo(StatisticsValue);

View File

@@ -2,45 +2,46 @@
/* Print /* Print
/*----------------------------------------------------------------*/ /*----------------------------------------------------------------*/
@media all { @media all {
/* Never show page breaks in normal view */ /* Never show page breaks in normal view */
.page-break-after, .page-break-after,
.page-break-before { .page-break-before {
display: none; display: none;
} }
} }
@media print { @media print {
/* html and body tweaks */ /* html and body tweaks */
html, body { html,
height: auto !important; body {
overflow: initial !important; height: auto !important;
background: none overflow: initial !important;
} background: none;
}
/* Page breaks */ /* Page breaks */
.page-break-after { .page-break-after {
display: block; display: block;
page-break-after: always; page-break-after: always;
position: relative; position: relative;
} }
.page-break-before { .page-break-before {
display: block; display: block;
page-break-before: always; page-break-before: always;
position: relative; position: relative;
} }
/* General styles */ /* General styles */
#fuse-toolbar, #fuse-toolbar,
#fuse-footer, #fuse-footer,
#fuse-navbar, #fuse-navbar,
#fuse-settings-presets, #fuse-settings-presets,
#fuse-layout .ps > .ps__rail-x, #fuse-layout .ps > .ps__rail-x,
#fuse-layout .ps > .ps__rail-y { #fuse-layout .ps > .ps__rail-y {
display: none !important; display: none !important;
} }
#fuse-layout .ps { #fuse-layout .ps {
overflow: visible !important; overflow: visible !important;
} }
} }

View File

@@ -1,212 +1,211 @@
code[class*="language-"], code[class*='language-'],
pre[class*="language-"] { pre[class*='language-'] {
text-align: left; text-align: left;
white-space: pre-wrap; white-space: pre-wrap;
word-break: break-all; word-break: break-all;
word-wrap: break-word; word-wrap: break-word;
color: #c3cee3; color: #c3cee3;
background: #263238; background: #263238;
font-family: Roboto Mono,"Liberation Mono",Menlo,Courier,monospace; font-family: Roboto Mono, 'Liberation Mono', Menlo, Courier, monospace;
font-size: 1em; font-size: 1em;
line-height: 1.5; line-height: 1.5;
-moz-tab-size: 4; -moz-tab-size: 4;
-o-tab-size: 4; -o-tab-size: 4;
tab-size: 4; tab-size: 4;
-webkit-hyphens: none; -webkit-hyphens: none;
-moz-hyphens: none; -moz-hyphens: none;
-ms-hyphens: none; -ms-hyphens: none;
hyphens: none; hyphens: none;
} }
code[class*='language-']::-moz-selection,
code[class*="language-"]::-moz-selection, pre[class*='language-']::-moz-selection,
pre[class*="language-"]::-moz-selection, code[class*='language-'] ::-moz-selection,
code[class*="language-"] ::-moz-selection, pre[class*='language-'] ::-moz-selection {
pre[class*="language-"] ::-moz-selection { background: #000000;
background: #000000;
} }
code[class*="language-"]::selection, code[class*='language-']::selection,
pre[class*="language-"]::selection, pre[class*='language-']::selection,
code[class*="language-"] ::selection, code[class*='language-'] ::selection,
pre[class*="language-"] ::selection { pre[class*='language-'] ::selection {
background: #000000; background: #000000;
} }
:not(pre) > code[class*="language-"] { :not(pre) > code[class*='language-'] {
white-space: normal; white-space: normal;
border-radius: 0.2em; border-radius: 0.2em;
padding: 0.1em; padding: 0.1em;
} }
pre[class*="language-"] { pre[class*='language-'] {
overflow: auto; overflow: auto;
position: relative; position: relative;
padding: 12px; padding: 12px;
border-radius: 4px;; border-radius: 4px;
} }
.language-css > code, .language-css > code,
.language-sass > code, .language-sass > code,
.language-scss > code { .language-scss > code {
color: #fd9170; color: #fd9170;
} }
[class*="language-"] .namespace { [class*='language-'] .namespace {
opacity: 0.7; opacity: 0.7;
} }
.token.plain-text { .token.plain-text {
color: #c3cee3; color: #c3cee3;
} }
.token.atrule { .token.atrule {
color: #c792ea; color: #c792ea;
} }
.token.attr-name { .token.attr-name {
color: #ffcb6b; color: #ffcb6b;
} }
.token.attr-value { .token.attr-value {
color: #c3e88d; color: #c3e88d;
} }
.token.attribute { .token.attribute {
color: #c3e88d; color: #c3e88d;
} }
.token.boolean { .token.boolean {
color: #c792ea; color: #c792ea;
} }
.token.builtin { .token.builtin {
color: #ffcb6b; color: #ffcb6b;
} }
.token.cdata { .token.cdata {
color: #80cbc4; color: #80cbc4;
} }
.token.char { .token.char {
color: #80cbc4; color: #80cbc4;
} }
.token.class { .token.class {
color: #ffcb6b; color: #ffcb6b;
} }
.token.class-name { .token.class-name {
color: #82aaff; color: #82aaff;
} }
.token.color { .token.color {
color: #f2ff00; color: #f2ff00;
} }
.token.comment { .token.comment {
color: #546e7a; color: #546e7a;
} }
.token.constant { .token.constant {
color: #c792ea; color: #c792ea;
} }
.token.deleted { .token.deleted {
color: #f07178; color: #f07178;
} }
.token.doctype { .token.doctype {
color: #546e7a; color: #546e7a;
} }
.token.entity { .token.entity {
color: #f07178; color: #f07178;
} }
.token.function { .token.function {
color: #c792ea; color: #c792ea;
} }
.token.hexcode { .token.hexcode {
color: #f2ff00; color: #f2ff00;
} }
.token.id { .token.id {
color: #c792ea; color: #c792ea;
font-weight: bold; font-weight: bold;
} }
.token.important { .token.important {
color: #c792ea; color: #c792ea;
font-weight: bold; font-weight: bold;
} }
.token.inserted { .token.inserted {
color: #80cbc4; color: #80cbc4;
} }
.token.keyword { .token.keyword {
color: #c792ea; color: #c792ea;
font-style: italic; font-style: italic;
} }
.token.number { .token.number {
color: #fd9170; color: #fd9170;
} }
.token.operator { .token.operator {
color: #89ddff; color: #89ddff;
} }
.token.prolog { .token.prolog {
color: #546e7a; color: #546e7a;
} }
.token.property { .token.property {
color: #80cbc4; color: #80cbc4;
} }
.token.pseudo-class { .token.pseudo-class {
color: #c3e88d; color: #c3e88d;
} }
.token.pseudo-element { .token.pseudo-element {
color: #c3e88d; color: #c3e88d;
} }
.token.punctuation { .token.punctuation {
color: #89ddff; color: #89ddff;
} }
.token.regex { .token.regex {
color: #f2ff00; color: #f2ff00;
} }
.token.selector { .token.selector {
color: #f07178; color: #f07178;
} }
.token.string { .token.string {
color: #c3e88d; color: #c3e88d;
} }
.token.symbol { .token.symbol {
color: #c792ea; color: #c792ea;
} }
.token.tag { .token.tag {
color: #f07178; color: #f07178;
} }
.token.unit { .token.unit {
color: #f07178; color: #f07178;
} }
.token.url { .token.url {
color: #fd9170; color: #fd9170;
} }
.token.variable { .token.variable {
color: #f07178; color: #f07178;
} }

View File

@@ -2,68 +2,68 @@
Basic Table Styles Basic Table Styles
*/ */
.table-responsive { .table-responsive {
display: block; display: block;
width: 100%; width: 100%;
overflow-x: auto; overflow-x: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar; -ms-overflow-style: -ms-autohiding-scrollbar;
} }
table.simple { table.simple {
width: 100%; width: 100%;
border: none; border: none;
border-spacing: 0; border-spacing: 0;
text-align: left; text-align: left;
} }
table.simple thead tr th { table.simple thead tr th {
padding: 16px 8px; padding: 16px 8px;
font-weight: 500; font-weight: 500;
border-bottom: 1px solid rgba(0, 0, 0, 0.12); border-bottom: 1px solid rgba(0, 0, 0, 0.12);
white-space: nowrap; white-space: nowrap;
} }
table.simple thead tr th:first-child { table.simple thead tr th:first-child {
padding-left: 24px; padding-left: 24px;
} }
table.simple thead tr th:last-child { table.simple thead tr th:last-child {
padding-right: 24px; padding-right: 24px;
} }
table.simple tbody tr td { table.simple tbody tr td {
padding: 12px 8px; padding: 12px 8px;
border-bottom: 1px solid rgba(0, 0, 0, 0.12); border-bottom: 1px solid rgba(0, 0, 0, 0.12);
} }
table.simple tbody tr td:first-child { table.simple tbody tr td:first-child {
padding-left: 24px; padding-left: 24px;
} }
table.simple tbody tr td:last-child { table.simple tbody tr td:last-child {
padding-right: 24px; padding-right: 24px;
} }
table.simple tbody tr:last-child td { table.simple tbody tr:last-child td {
border-bottom: none; border-bottom: none;
} }
table.simple.clickable tbody tr { table.simple.clickable tbody tr {
cursor: pointer; cursor: pointer;
} }
table.simple.clickable tbody tr:hover { table.simple.clickable tbody tr:hover {
background: rgba(0, 0, 0, 0.03); background: rgba(0, 0, 0, 0.03);
} }
table.simple.borderless { table.simple.borderless {
border: none; border: none;
} }
table.simple.borderless tbody tr td{ table.simple.borderless tbody tr td {
border: none; border: none;
} }
table.simple.borderless thead tr th{ table.simple.borderless thead tr th {
border: none; border: none;
} }