Compare commits

..

18 Commits

Author SHA1 Message Date
afed3aed53 Merge pull request 'RC-9-property-service' (#10) from RC-9-property-service into dev
Reviewed-on: #10
2023-10-12 10:31:16 +03:00
ead8f23379 Merge branch 'dev' of https://gitea.a3-global.com/sysadminix/rentalcalculator into RC-9-property-service 2023-10-12 08:29:11 +01:00
4d31f9f71a Merge pull request 'RC-13: create search and buy page' (#9) from RC-13-rent-and-buy-page into dev
Reviewed-on: #9
2023-10-12 10:28:27 +03:00
880df2c2ac RC-13: create search and buy page 2023-10-12 08:20:13 +01:00
c40302aa93 RC-9: create property service 2023-10-07 12:35:40 +01:00
b424376656 Merge pull request 'RC-14: create dashboard page' (#8) from RC-14-dashboard-page into dev
Reviewed-on: #8
2023-09-17 18:31:11 +03:00
788ca3519f RC-14: create dashboard page 2023-09-17 16:30:08 +01:00
3a0f43d491 Merge pull request 'RC-12-registration-popup' (#7) from RC-12-registration-popup into dev
Reviewed-on: #7
2023-08-27 17:44:51 +03:00
6ecb29eb5e RC-12: create registration popup component 2023-08-27 15:43:32 +01:00
96a838eb8e RC-12: create basic popup component 2023-08-27 15:43:20 +01:00
8795de6c7d RC-12: update layout2 2023-08-27 15:43:04 +01:00
5bc0e9220a RC-10: create configs/consts and moved auth roles to that file 2023-08-27 15:42:07 +01:00
d05052b5e3 Merge pull request 'RC-10-favorites-and-history-cards' (#6) from RC-10-favorites-and-history-cards into dev
Reviewed-on: #6
2023-08-27 14:02:14 +03:00
15f9ae928c RC-10: update tailwind config 2023-08-27 12:00:46 +01:00
6983d5724a RC-10: create configs/consts and moved auth roles to that file 2023-08-27 12:00:28 +01:00
36cb82d335 RC-10: update layout1 2023-08-27 11:58:27 +01:00
993bf970d1 RC-10: create history and favorite pages 2023-08-27 11:58:13 +01:00
0db5333242 RC-10: add new values, reducers and selectors for history and favorites to user slice 2023-08-27 11:57:39 +01:00
35 changed files with 637 additions and 135 deletions

View File

@@ -1,11 +0,0 @@
/**
* Authorization Roles
*/
const authRoles = {
admin: ['admin'],
staff: ['admin', 'staff'],
user: ['admin', 'staff', 'user'],
onlyGuest: [],
};
export default authRoles;

19
src/app/configs/consts.js Normal file
View File

@@ -0,0 +1,19 @@
export const STATISTICS_MODES = {
positive: 'positive',
extra_positive: 'extra_positive',
negative: 'negative',
extra_negative: 'extra_negative',
};
export const PROPERTIES_LAYOUTS = {
list: 'list',
grid: 'grid',
};
// Authorization Roles
export const authRoles = {
admin: ['admin'],
staff: ['admin', 'staff'],
user: ['admin', 'staff', 'user'],
onlyGuest: [],
};

View File

@@ -6,8 +6,9 @@ import Error404Page from '../main/404/Error404Page';
import navigationPagesConfigs from '../main/navigationPages/navigationPagesConfig'; import navigationPagesConfigs from '../main/navigationPages/navigationPagesConfig';
import authPagesConfigs from '../main/authPages/authPagesConfigs'; import authPagesConfigs from '../main/authPages/authPagesConfigs';
import HomeConfig from '../main/home/HomeConfig'; import HomeConfig from '../main/home/HomeConfig';
import RentAndBuyConfig from '../main/rentAndBuy/RentAndBuyConfig';
const routeConfigs = [...navigationPagesConfigs, ...authPagesConfigs, HomeConfig]; const routeConfigs = [...navigationPagesConfigs, ...authPagesConfigs, HomeConfig, RentAndBuyConfig];
const routes = [ const routes = [
...FuseUtils.generateRoutesFromConfigs(routeConfigs, settingsConfig.defaultAuth), ...FuseUtils.generateRoutesFromConfigs(routeConfigs, settingsConfig.defaultAuth),

View File

@@ -1,7 +1,7 @@
import i18next from 'i18next'; import i18next from 'i18next';
import ForgotPasswordPage from './ForgotPasswordPage'; import ForgotPasswordPage from './ForgotPasswordPage';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'forgotPasswordPage', en); i18next.addResourceBundle('en', 'forgotPasswordPage', en);

View File

@@ -1,7 +1,7 @@
import i18next from 'i18next'; import i18next from 'i18next';
import SignInPage from './SignInPage'; import SignInPage from './SignInPage';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'signInPage', en); i18next.addResourceBundle('en', 'signInPage', en);

View File

@@ -1,7 +1,7 @@
import i18next from 'i18next'; import i18next from 'i18next';
import SignUpPage from './SignUpPage'; import SignUpPage from './SignUpPage';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'signUpPage', en); i18next.addResourceBundle('en', 'signUpPage', en);

View File

@@ -26,7 +26,7 @@ function AboutUs({ t }) {
</div> </div>
<Link <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" 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" to="/rent-and-buy/search"
> >
{t('research_btn')} {t('research_btn')}
</Link> </Link>

View File

@@ -1,19 +1,25 @@
import { memo, useState } from 'react'; import { memo, useState } from 'react';
import { withTranslation } from 'react-i18next'; import { withTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import SearchInput from '../../shared-components/SearchInput'; import SearchInput from '../../shared-components/SearchInput';
function Welcome({ t }) { function Welcome({ t }) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
const navigate = useNavigate();
const onInputType = (event) => { const onInputType = (event) => {
const { target } = event; const { target } = event;
const value = target?.value ?? ''; const value = target?.value ?? '';
console.log(value);
setQuery(value); setQuery(value);
}; };
const onSearch = () => { const onSearch = () => {
// query const trimmedQuery = query.trim();
if (!trimmedQuery) {
return;
}
navigate(`/rent-and-buy/search?query=${trimmedQuery}`);
}; };
return ( return (
@@ -26,6 +32,7 @@ function Welcome({ t }) {
{t('subtitle')} {t('subtitle')}
</p> </p>
<SearchInput <SearchInput
className="max-w-[780px]"
mode="simple" mode="simple"
placeholder={t('main_input_placeholder')} placeholder={t('main_input_placeholder')}
btnText={t('main_input_btn')} btnText={t('main_input_btn')}

View File

@@ -1,7 +1,43 @@
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import FusePageSimple from '@fuse/core/FusePageSimple'; import FusePageSimple from '@fuse/core/FusePageSimple';
import DemoContent from '@fuse/core/DemoContent'; import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';
import { useState } from 'react';
import { withTranslation } from 'react-i18next';
import SearchInput from '../../shared-components/SearchInput';
import DashboardCategory from '../shared-components/DashboardCategory';
const categoriesMock = [
{
title: 'All Properties',
value: 34,
valueColor: 'secondary-main',
},
{
title: 'New',
value: 12,
valueColor: 'common-highlight2',
},
{
title: 'In Research',
value: 3,
valueColor: 'secondary-main',
},
{
title: 'Interested',
value: 25,
valueColor: 'common-highlight1',
},
{
title: 'Purchased',
value: 8,
valueColor: 'accept-main',
},
{
title: 'Not Interested',
value: 11,
valueColor: 'error-main',
},
];
const Root = styled(FusePageSimple)(({ theme }) => ({ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-header': { '& .FusePageSimple-header': {
@@ -16,21 +52,39 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-sidebarContent': {}, '& .FusePageSimple-sidebarContent': {},
})); }));
function DashboardPage(props) { function DashboardPage({ t }) {
const { t } = useTranslation('dashboardPage'); const [query, setQuery] = useState('');
const onInputType = (event) => {
const { target } = event;
const value = target?.value ?? '';
setQuery(value);
};
const onSearch = () => {
// query
};
return ( return (
<Root <Root
// header={
// <div className="p-24">
// <h4>{t('TITLE')}</h4>
// </div>
// }
content={ content={
<div className="p-24"> <div className="w-full p-60">
<h4>Content</h4> <div className="flex flex-wrap justify-center items-center gap-20 mb-52">
<br /> {categoriesMock.map(({ title, value, valueColor }) => (
<DemoContent /> <DashboardCategory title={title} value={value} valueColor={valueColor} />
))}
</div>
<SearchInput
className="mb-28"
mode="manual"
btnText={t('search_input_btn')}
query={query}
onType={onInputType}
onSearch={onSearch}
/>
<Paper className="w-full h-640 mb-[30px] rounded-20 shadow-light" />
</div> </div>
} }
scroll="content" scroll="content"
@@ -38,4 +92,4 @@ function DashboardPage(props) {
); );
} }
export default DashboardPage; export default withTranslation('dashboardPage')(DashboardPage);

View File

@@ -1,6 +1,6 @@
import i18next from 'i18next'; import i18next from 'i18next';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import Dashboard from './Dashboard'; import Dashboard from './Dashboard';
import en from './i18n/en'; import en from './i18n/en';

View File

@@ -1,3 +1,5 @@
const locale = {}; const locale = {
search_input_btn: 'calculate',
};
export default locale; export default locale;

View File

@@ -1,7 +1,15 @@
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import FusePageSimple from '@fuse/core/FusePageSimple'; import FusePageSimple from '@fuse/core/FusePageSimple';
import DemoContent from '@fuse/core/DemoContent'; import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';
import { PROPERTIES_LAYOUTS } from 'app/configs/consts';
import { selectUserFavorites, updateUserFavorites } from 'app/store/userSlice';
import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { usePropertiesHeader } from 'src/app/hooks';
import PropertiesHeader from '../shared-components/PropertiesHeader';
import PropertyGridCard from '../shared-components/PropertyGridCard';
import PropertyListItem from '../shared-components/PropertyListItem';
const Root = styled(FusePageSimple)(({ theme }) => ({ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-header': { '& .FusePageSimple-header': {
@@ -16,21 +24,73 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-sidebarContent': {}, '& .FusePageSimple-sidebarContent': {},
})); }));
function FavoritesPage(props) { function FavoritesPage() {
const { t } = useTranslation('favoritesPage'); const dispatch = useDispatch();
const items = useSelector(selectUserFavorites);
const { categories, activeCategory, onCategory, layouts, activeLayout, onLayout, onItemDelete } =
usePropertiesHeader(items);
const onFavorite = useCallback(
(id) => {
const targetItem = items.find((item) => item.id === id);
if (!targetItem) {
return;
}
onItemDelete(targetItem?.category);
dispatch(updateUserFavorites(targetItem)).catch((error) => console.log(error));
},
[items, onItemDelete, dispatch]
);
const renderedItems = useMemo(
() =>
items.map((item, idx) => {
if (activeCategory !== 'all' && item.category !== activeCategory) {
return;
}
// eslint-disable-next-line consistent-return
return activeLayout === PROPERTIES_LAYOUTS.list ? (
<PropertyListItem
{...item}
key={item.title + idx}
onDelete={onFavorite}
onFavorite={onFavorite}
/>
) : (
<PropertyGridCard
{...item}
key={item.title + idx}
onDelete={onFavorite}
onFavorite={onFavorite}
/>
);
}),
[items, activeCategory, activeLayout, onFavorite]
);
return ( return (
<Root <Root
// header={
// <div className="p-24">
// <h4>{t('TITLE')}</h4>
// </div>
// }
content={ content={
<div className="p-24"> <div className="w-full p-60">
<h4>Content</h4> <Paper className="w-full h-320 mb-[30px] rounded-20 shadow-light" />
<br /> <PropertiesHeader
<DemoContent /> className="mb-40"
categories={categories}
layouts={layouts}
onCategory={onCategory}
onLayout={onLayout}
/>
<div
className={clsx(
'w-full flex flex-wrap justify-center gap-28',
activeLayout === PROPERTIES_LAYOUTS.list && 'flex-col'
)}
>
{renderedItems}
</div>
</div> </div>
} }
scroll="content" scroll="content"

View File

@@ -1,11 +1,13 @@
import { lazy } from 'react';
import i18next from 'i18next'; import i18next from 'i18next';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import Favorites from './Favorites';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'favoritesPage', en); i18next.addResourceBundle('en', 'favoritesPage', en);
const Favorites = lazy(() => import('./Favorites'));
const FavoritesConfig = { const FavoritesConfig = {
settings: { settings: {
layout: { layout: {
@@ -22,28 +24,3 @@ const FavoritesConfig = {
}; };
export default FavoritesConfig; export default FavoritesConfig;
/**
* Lazy load Example
*/
/*
import React from 'react';
const Example = lazy(() => import('./Example'));
const ExampleConfig = {
settings: {
layout: {
config: {},
},
},
routes: [
{
path: 'example',
element: <Example />,
},
],
};
export default ExampleConfig;
*/

View File

@@ -1,7 +1,15 @@
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import FusePageSimple from '@fuse/core/FusePageSimple'; import FusePageSimple from '@fuse/core/FusePageSimple';
import DemoContent from '@fuse/core/DemoContent'; import Paper from '@mui/material/Paper';
import { styled } from '@mui/material/styles';
import { PROPERTIES_LAYOUTS } from 'app/configs/consts';
import { selectUserHistory, updateUserFavorites, updateUserHistory } from 'app/store/userSlice';
import clsx from 'clsx';
import { useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { usePropertiesHeader } from 'src/app/hooks';
import PropertiesHeader from '../shared-components/PropertiesHeader';
import PropertyGridCard from '../shared-components/PropertyGridCard';
import PropertyListItem from '../shared-components/PropertyListItem';
const Root = styled(FusePageSimple)(({ theme }) => ({ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-header': { '& .FusePageSimple-header': {
@@ -16,21 +24,83 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
'& .FusePageSimple-sidebarContent': {}, '& .FusePageSimple-sidebarContent': {},
})); }));
function HistoryPage(props) { function HistoryPage() {
const { t } = useTranslation('historyPage'); const dispatch = useDispatch();
const items = useSelector(selectUserHistory);
const { categories, activeCategory, onCategory, layouts, activeLayout, onLayout, onItemDelete } =
usePropertiesHeader(items);
const onDelete = useCallback(
(id) => {
const targetItem = items.find((item) => item.id === id);
const newHistory = items.filter((item) => item.id !== id);
onItemDelete(targetItem?.category);
dispatch(updateUserHistory(newHistory));
},
[items, onItemDelete, dispatch]
);
const onFavorite = useCallback(
(id) => {
const targetItem = items.find((item) => item.id === id);
if (!targetItem) {
return;
}
dispatch(updateUserFavorites(targetItem)).catch((error) => console.log(error));
},
[items, dispatch]
);
const renderedItems = useMemo(
() =>
items.map((item, idx) => {
if (activeCategory !== 'all' && item.category !== activeCategory) {
return;
}
// eslint-disable-next-line consistent-return
return activeLayout === PROPERTIES_LAYOUTS.list ? (
<PropertyListItem
{...item}
key={item.title + idx}
onDelete={onDelete}
onFavorite={onFavorite}
/>
) : (
<PropertyGridCard
{...item}
key={item.title + idx}
onDelete={onDelete}
onFavorite={onFavorite}
/>
);
}),
[items, activeCategory, activeLayout, onDelete, onFavorite]
);
return ( return (
<Root <Root
// header={
// <div className="p-24">
// <h4>{t('TITLE')}</h4>
// </div>
// }
content={ content={
<div className="p-24"> <div className="w-full p-60">
<h4>Content</h4> <Paper className="w-full h-320 mb-[30px] rounded-20 shadow-light" />
<br /> <PropertiesHeader
<DemoContent /> className="mb-40"
categories={categories}
layouts={layouts}
onCategory={onCategory}
onLayout={onLayout}
/>
<div
className={clsx(
'w-full flex flex-wrap justify-center gap-28',
activeLayout === PROPERTIES_LAYOUTS.list && 'flex-col'
)}
>
{renderedItems}
</div>
</div> </div>
} }
scroll="content" scroll="content"

View File

@@ -1,11 +1,13 @@
import { lazy } from 'react';
import i18next from 'i18next'; import i18next from 'i18next';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import History from './History';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'historyPage', en); i18next.addResourceBundle('en', 'historyPage', en);
const History = lazy(() => import('./History'));
const HistoryConfig = { const HistoryConfig = {
settings: { settings: {
layout: { layout: {
@@ -22,28 +24,3 @@ const HistoryConfig = {
}; };
export default HistoryConfig; export default HistoryConfig;
/**
* Lazy load Example
*/
/*
import React from 'react';
const Example = lazy(() => import('./Example'));
const ExampleConfig = {
settings: {
layout: {
config: {},
},
},
routes: [
{
path: 'example',
element: <Example />,
},
],
};
export default ExampleConfig;
*/

View File

@@ -1,7 +1,7 @@
import { lazy } from 'react'; import { lazy } from 'react';
import i18next from 'i18next'; import i18next from 'i18next';
import authRoles from '../../../configs/authRoles'; import { authRoles } from '../../../configs/consts';
import en from './i18n/en'; import en from './i18n/en';
i18next.addResourceBundle('en', 'profilePage', en); i18next.addResourceBundle('en', 'profilePage', en);

View File

@@ -0,0 +1,27 @@
import Typography from '@mui/material/Typography';
import clsx from 'clsx';
import { memo } from 'react';
function DashboardCategory({ className, title, value, valueColor }) {
return (
<button
type="button"
className={clsx(
'flex flex-col items-center justify-center gap-24 max-w-224 w-full h-160 p-24 bg-white shadow-light rounded-20',
className
)}
>
<Typography variant="body1" className="text-lg text-common-layout font-medium">
{title}
</Typography>
<Typography
variant="body2"
className={clsx('text-5xl font-bold', valueColor && `text-${valueColor}`)}
>
{value}
</Typography>
</button>
);
}
export default memo(DashboardCategory);

View File

@@ -0,0 +1,16 @@
import Typography from '@mui/material/Typography';
import { withTranslation } from 'react-i18next';
import { Outlet } from 'react-router-dom';
function RentAndBuy({ t }) {
return (
<div className="flex flex-col max-w-8xl w-full px-10 py-32 mx-auto">
<Typography variant="h1" className="mb-44 text-4xl text-common-layout font-medium">
{t('title')}
</Typography>
<Outlet />
</div>
);
}
export default withTranslation('rentAndBuyPage')(RentAndBuy);

View File

@@ -0,0 +1,38 @@
import { lazy } from 'react';
import i18next from 'i18next';
import RentAndBuy from './RentAndBuy';
import en from './i18n/en';
i18next.addResourceBundle('en', 'rentAndBuyPage', en);
const SearchAddress = lazy(() => import('./components/SearchAddress/SearchAddress'));
const PropertyPreview = lazy(() => import('./components/PropertyPreview/PropertyPreview'));
const RentAndBuyConfig = {
settings: {
layout: {
config: {},
style: 'layout2',
},
},
auth: null,
routes: [
{
path: '/rent-and-buy',
element: <RentAndBuy />,
children: [
{
path: 'search',
element: <SearchAddress />,
},
{
path: 'preview',
element: <PropertyPreview />,
},
],
},
],
};
export default RentAndBuyConfig;

View File

@@ -0,0 +1,32 @@
import Button from '@mui/material/Button';
import { useState } from 'react';
import { withTranslation } from 'react-i18next';
import RegistrationPopup from 'src/app/main/shared-components/popups/RegistrationPopup';
function PropertyPreview({ t }) {
const [isPopupOpen, setIsPopupOpen] = useState(false);
const openPopup = () => setIsPopupOpen(true);
const closePopup = () => setIsPopupOpen(false);
return (
<>
<div className="flex flex-col items-center">
<Button
variant="contained"
color="secondary"
className="w-384 p-20 text-2xl leading-none rounded-lg"
aria-label={t('see_more_btn')}
type="button"
size="large"
onClick={openPopup}
>
{t('see_more_btn')}
</Button>
</div>
<RegistrationPopup open={isPopupOpen} onClose={closePopup} />
</>
);
}
export default withTranslation('rentAndBuyPage')(PropertyPreview);

View File

@@ -0,0 +1,18 @@
import { withTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
function SearchAddress({ t }) {
return (
<div className="flex flex-col items-center gap-68">
<span>How are you?</span>
<Link
to="/rent-and-buy/preview"
className="inline-block w-[182px] py-[17px] text-center text-base text-primary-light font-semibold tracking-widest uppercase rounded-lg bg-secondary-light shadow hover:shadow-hover hover:shadow-secondary-light ease-in-out duration-300"
>
{t('show_btn')}
</Link>
</div>
);
}
export default withTranslation('rentAndBuyPage')(SearchAddress);

View File

@@ -0,0 +1,8 @@
const locale = {
title: 'Rent&Buy Analysis',
show_btn: 'show',
see_more_btn: 'See more',
view: 'View the Calculation',
};
export default locale;

View File

@@ -0,0 +1,11 @@
import Paper from '@mui/material/Paper';
function PropertyAnalysisHeader() {
return (
<Paper>
<div></div>
</Paper>
);
}
export default PropertyAnalysisHeader;

View File

@@ -1,7 +1,14 @@
import { forwardRef, memo, useCallback } from 'react';
import TextField from '@mui/material/TextField';
import Button from '@mui/material/Button';
import _ from '@lodash'; import _ from '@lodash';
import Button from '@mui/material/Button';
// import Select from '@mui/material/Select';
import TextField from '@mui/material/TextField';
import clsx from 'clsx';
import { forwardRef, memo, useCallback } from 'react';
const SEARCH_INPUT_MODES = {
simple: 'simple',
manual: 'manual',
};
const StyledTextField = forwardRef((props, ref) => ( const StyledTextField = forwardRef((props, ref) => (
<TextField <TextField
@@ -19,28 +26,32 @@ const StyledTextField = forwardRef((props, ref) => (
/> />
)); ));
function SearchInput({ mode, placeholder, btnText, query, onType, onSearch }) { function SearchInput({ className, mode, placeholder, btnText, query, onType, onSearch }) {
const isSimpleMode = mode === 'simple'; const isSimpleMode = mode === SEARCH_INPUT_MODES.simple;
const isManualMode = mode === SEARCH_INPUT_MODES.manual;
const hasBtn = isSimpleMode || isManualMode;
const debouncedOnType = useCallback(_.debounce(onType, 250), [onType]); const debouncedOnType = useCallback(_.debounce(onType, 250), [onType]);
return ( return (
<form className="flex items-center gap-20"> <form className={clsx('flex items-center gap-20', className)}>
<StyledTextField <StyledTextField
type="text" type="text"
variant="outlined" variant="outlined"
className="max-w-[620px] w-full bourder-0" className="w-full bourder-0"
defaultValue={query} defaultValue={query}
placeholder={placeholder} placeholder={placeholder ?? ''}
onChange={debouncedOnType} onChange={debouncedOnType}
/> />
{isSimpleMode && ( {/* {isManualMode && <Select />} */}
{hasBtn && (
<Button <Button
variant="contained" variant="contained"
color="inherit" color="inherit"
className="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" className="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"
aria-label={btnText} aria-label={btnText ?? ''}
type="button" type="button"
size="large" size="large"
onClick={onSearch} onClick={onSearch}

View File

@@ -0,0 +1,44 @@
import Backdrop from '@mui/material/Backdrop';
import Box from '@mui/material/Box';
import Fade from '@mui/material/Fade';
import Modal from '@mui/material/Modal';
import { memo } from 'react';
const style = {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
bgcolor: 'inherit',
boxShadow: 24,
overflow: 'hidden',
};
function BasicPopup({ children, className, open, onClose }) {
return (
<div>
<Modal
aria-labelledby="transition-modal-title"
aria-describedby="transition-modal-description"
open={open}
onClose={onClose}
closeAfterTransition
slots={{ backdrop: Backdrop }}
slotProps={{
backdrop: {
timeout: 500,
},
}}
>
<Fade in={open}>
<Box sx={style} className={className}>
{children}
</Box>
</Fade>
</Modal>
</div>
);
}
export default memo(BasicPopup);

View File

@@ -0,0 +1,72 @@
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import { memo } from 'react';
import { Link } from 'react-router-dom';
import BasicPopup from './BasicPopup';
const bullets = [
'Lorem ipsum rci egestas. Tortor nulla ac est nulla nisl ut.',
'Lorem ipsum dolor sit amet consectetur.',
'Lorem ipsum rci egestas. Tortor nulla ac est nulla nisl ut.',
'Lorem ipsum dolor sit amet consectetur.',
'Lorem ipsum dolor sit amet consectetur.',
'Lorem ipsum dt amet consectetur. Duis massa vel estas. Tortor nulla ac est nulla nisl ut.',
'Lorem ipsum dolor sit amet consectetur.',
];
function RegistrationPopup({ open, onClose }) {
return (
<BasicPopup
className="flex max-w-[76vw] w-full h-[66vh] min-h-[600px] rounded-20"
open={open}
onClose={onClose}
>
<Box
className="relative hidden md:flex flex-auto items-center justify-center h-full p-64 lg:px-112 overflow-hidden max-w-[45vw] w-full"
sx={{ backgroundColor: 'common.layout' }}
>
<svg
className="absolute inset-0 pointer-events-none"
viewBox="0 0 960 540"
width="100%"
height="100%"
preserveAspectRatio="xMidYMax slice"
xmlns="http://www.w3.org/2000/svg"
>
<Box
component="g"
sx={{ color: 'primary.light' }}
className="opacity-20"
fill="none"
stroke="currentColor"
strokeWidth="100"
>
<circle r="234" cx="266" cy="23" />
<circle r="234" cx="790" cy="551" />
</Box>
</svg>
</Box>
<Box
className="flex flex-col items-center px-60 pt-56"
sx={{ backgroundColor: 'background.paper' }}
>
<Typography variant="h4" className="mb-52 text-4xl font-semibold">
Lorem ipsum dolor sit amet consetur
</Typography>
<ul className="flex flex-col gap-10 mb-68">
{bullets.map((bullet) => (
<li className="bullet">{bullet}</li>
))}
</ul>
<Link
className="w-full py-20 text-center text-xl text-primary-light font-semibold tracking-widest rounded-2xl bg-secondary-main shadow hover:shadow-hover hover:shadow-secondary-main ease-in-out duration-300"
to="/sign-up"
>
Try for free
</Link>
</Box>
</BasicPopup>
);
}
export default memo(RegistrationPopup);

View File

@@ -20,7 +20,7 @@ export default class AuthService extends FuseUtils.EventEmitter {
.then((userCredential) => firebaseAuth.updateProfile(userCredential.user, { displayName })) .then((userCredential) => firebaseAuth.updateProfile(userCredential.user, { displayName }))
.then(() => { .then(() => {
const userRef = firebaseDb.ref(this.#db, `users/${this.#auth.currentUser.uid}`); const userRef = firebaseDb.ref(this.#db, `users/${this.#auth.currentUser.uid}`);
const value = { role: 'user', data: { displayName, email } }; const value = { role: 'user', data: { displayName, email }, favorites: [] };
return firebaseDb.set(userRef, value); return firebaseDb.set(userRef, value);
}) })

View File

@@ -1,5 +1,7 @@
import AuthService from './authService'; import AuthService from './authService';
import FirebaseService from './firebaseService'; import FirebaseService from './firebaseService';
import PropertyService from './propertyService';
// TODO: change to firebase secrets or to use firebase functions // TODO: change to firebase secrets or to use firebase functions
const firebaseConfig = { const firebaseConfig = {
apiKey: 'AIzaSyBqMGmOF0-DkYDpnsmZwpf5S8w5cL3fBb8', apiKey: 'AIzaSyBqMGmOF0-DkYDpnsmZwpf5S8w5cL3fBb8',
@@ -12,7 +14,15 @@ const firebaseConfig = {
measurementId: 'G-JW7J8ZQ9FJ', measurementId: 'G-JW7J8ZQ9FJ',
}; };
// TopHap
const propertyConfig = {
dataBaseURL: process.env.REACT_APP_PROPERTY_DATA_BASE_URL,
widgetBaseURL: process.env.REACT_APP_PROPERTY_WIDGET_BASE_URL,
apiKey: process.env.REACT_APP_PROPERTY_API_KEY,
};
const firebase = new FirebaseService(firebaseConfig); const firebase = new FirebaseService(firebaseConfig);
const authService = new AuthService(); const authService = new AuthService();
const propertyService = new PropertyService(propertyConfig);
export { authService, firebase }; export { authService, firebase, propertyService };

View File

@@ -0,0 +1,57 @@
import axios from 'axios';
const widgets = {
absorptionRate: 'absorption-rate',
crime: 'crime',
hazards: 'hazards',
mapPreview: 'map-preview',
marketTrends: 'market-trends',
noise: 'noise',
population: 'population',
propertyTypes: 'property-types',
rentEstimate: 'rent-estimate',
thEstimate: 'th-estimate',
turnover: 'turnover',
walkability: 'walkability',
zipCodeMap: 'zip-code-map',
};
export default class PropertyService {
#dataApi;
#widgetApi;
constructor(config) {
const { dataBaseURL, widgetBaseURL, apiKey } = config;
this.#dataApi = axios.create({
baseURL: dataBaseURL,
headers: { 'X-API-Key': apiKey },
});
this.#widgetApi = axios.create({
baseURL: widgetBaseURL,
params: {
sid: apiKey,
},
});
}
fetchProperty(params) {
return this.#dataApi.get('/property', { params });
}
search(body) {
return this.#dataApi.post('/search', body);
}
fetchComparables(params) {
return this.#dataApi.get('/comparables', { params });
}
fetchWidgetByPropertyId(widget, propertyId, params) {
return this.#widgetApi.get(`${widget}/${propertyId}`, { params });
}
fetchWidgetByAddress(widget, params) {
return this.#widgetApi.get(`${widget}/address`, { params });
}
}

View File

@@ -2,7 +2,7 @@ const config = {
title: 'Layout 1 - Dashboard', title: 'Layout 1 - Dashboard',
defaults: { defaults: {
mode: 'container', mode: 'container',
containerWidth: 1570, containerWidth: 1590,
navbar: { navbar: {
display: true, display: true,
folded: true, folded: true,

View File

@@ -6,7 +6,7 @@ import { navbarCloseMobile, selectFuseNavbar } from 'app/store/fuse/navbarSlice'
import { selectFuseCurrentLayoutConfig } from 'app/store/fuse/settingsSlice'; import { selectFuseCurrentLayoutConfig } from 'app/store/fuse/settingsSlice';
import NavbarLayout1Content from './NavbarLayout1Content'; import NavbarLayout1Content from './NavbarLayout1Content';
const navbarWidth = 280; const navbarWidth = 330;
const StyledNavBar = styled('div')(({ theme, open, position }) => ({ const StyledNavBar = styled('div')(({ theme, open, position }) => ({
minWidth: navbarWidth, minWidth: navbarWidth,

View File

@@ -7,7 +7,7 @@ function FooterLayout2() {
const { t } = useTranslation('layout2'); const { t } = useTranslation('layout2');
return ( return (
<footer className="flex items-center justify-center w-full bg-common-layout"> <footer className="z-[10000] flex items-center justify-center w-full bg-common-layout">
<div className="flex gap-96 w-full max-w-screen-xl px-10 py-52"> <div className="flex gap-96 w-full max-w-screen-xl px-10 py-52">
<ul className="flex flex-col gap-16 mr-96"> <ul className="flex flex-col gap-16 mr-96">
<li> <li>

View File

@@ -8,7 +8,7 @@ function HeaderLayout2(props) {
const { t } = useTranslation('layout2'); const { t } = useTranslation('layout2');
return ( return (
<header className="fixed z-50 flex items-center justify-center w-full h-72 px-10 bg-primary-light"> <header className="fixed z-[10000] flex items-center justify-center w-full h-72 px-10 bg-primary-light">
<div className="flex justify-between max-w-screen-xl w-full"> <div className="flex justify-between max-w-screen-xl w-full">
<Link to="/"> <Link to="/">
<img <img

View File

@@ -7,7 +7,7 @@ function NavLinks({ className }) {
return ( return (
<> <>
<Link className={className} to="/rent-and-buy"> <Link className={className} to="/rent-and-buy/search">
{t('rent_and_buy')} {t('rent_and_buy')}
</Link> </Link>
<Link className={className} to={{ hash: 'about-us' }}> <Link className={className} to={{ hash: 'about-us' }}>

View File

@@ -186,6 +186,8 @@ module.exports = {
primary: '#151B30', primary: '#151B30',
secondary: '#6D6D6D', secondary: '#6D6D6D',
disabled: '#D9D9D9', disabled: '#D9D9D9',
highlight1: '#FFBC6E',
highlight2: '#DB00FF',
}, },
primary: { primary: {
light: '#FFFFFF', light: '#FFFFFF',