Compare commits
8 Commits
dev
...
2e3adc86a9
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e3adc86a9 | |||
| 425a9abe92 | |||
| 2160d206e9 | |||
| 120ad1b4f7 | |||
| 3c3ec7da11 | |||
| d944147a77 | |||
| 09d5bac9a7 | |||
| 70c57feaa8 |
11
src/app/configs/authRoles.js
Normal file
11
src/app/configs/authRoles.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/**
|
||||||
|
* Authorization Roles
|
||||||
|
*/
|
||||||
|
const authRoles = {
|
||||||
|
admin: ['admin'],
|
||||||
|
staff: ['admin', 'staff'],
|
||||||
|
user: ['admin', 'staff', 'user'],
|
||||||
|
onlyGuest: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default authRoles;
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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: [],
|
|
||||||
};
|
|
||||||
@@ -6,9 +6,8 @@ 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, RentAndBuyConfig];
|
const routeConfigs = [...navigationPagesConfigs, ...authPagesConfigs, HomeConfig];
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
...FuseUtils.generateRoutesFromConfigs(routeConfigs, settingsConfig.defaultAuth),
|
...FuseUtils.generateRoutesFromConfigs(routeConfigs, settingsConfig.defaultAuth),
|
||||||
|
|||||||
@@ -6,63 +6,6 @@ import { showMessage } from 'app/store/fuse/messageSlice';
|
|||||||
import { logoutUser, setUser } from 'app/store/userSlice';
|
import { logoutUser, setUser } from 'app/store/userSlice';
|
||||||
import { authService, firebase } from '../services';
|
import { authService, firebase } from '../services';
|
||||||
|
|
||||||
const cards = [
|
|
||||||
{
|
|
||||||
id: '123',
|
|
||||||
image:
|
|
||||||
'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
|
||||||
title: '6 Via delle Crosarolle, Crosarolle, Veneto, Crosarolle, Veneto',
|
|
||||||
category: 'buy',
|
|
||||||
status: 'new',
|
|
||||||
favorite: false,
|
|
||||||
update: '12.12.2022',
|
|
||||||
statistics: [
|
|
||||||
{ subject: 'Monthly Cash Flow', value: '$ 78,000', mode: 'extra_positive' },
|
|
||||||
{ subject: 'Cash on Cash Return', value: '78%', mode: 'positive' },
|
|
||||||
{ subject: 'Selling Prise', value: '$500,000', mode: '' },
|
|
||||||
{ subject: 'Cash Out of Pocket', value: '$125,000', mode: '' },
|
|
||||||
{ subject: 'Annual Revenue', value: '$5,000', mode: '' },
|
|
||||||
{ subject: 'Annual Net Profit', value: '+$50,000', mode: 'extra_positive' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '456',
|
|
||||||
image:
|
|
||||||
'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
|
||||||
title: '6 Via delle Crosarolle, Crosarolle, Veneto',
|
|
||||||
category: 'buy',
|
|
||||||
status: 'new',
|
|
||||||
favorite: false,
|
|
||||||
update: '12.12.2022',
|
|
||||||
statistics: [
|
|
||||||
{ subject: 'Monthly Cash Flow', value: '$ 78,000', mode: 'extra_negative' },
|
|
||||||
{ subject: 'Cash on Cash Return', value: '78%', mode: 'negative' },
|
|
||||||
{ subject: 'Selling Prise', value: '$500,000', mode: '' },
|
|
||||||
{ subject: 'Cash Out of Pocket', value: '$125,000', mode: '' },
|
|
||||||
{ subject: 'Annual Revenue', value: '$5,000', mode: '' },
|
|
||||||
{ subject: 'Annual Net Profit', value: '+$50,000', mode: 'extra_negative' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: '789',
|
|
||||||
image:
|
|
||||||
'https://images.unsplash.com/photo-1600585154340-be6161a56a0c?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1170&q=80',
|
|
||||||
title: '6 Via delle Crosarolle, Crosarolle, Veneto',
|
|
||||||
category: 'rent',
|
|
||||||
status: 'new',
|
|
||||||
favorite: false,
|
|
||||||
update: '12.12.2022',
|
|
||||||
statistics: [
|
|
||||||
{ subject: 'Monthly Cash Flow', value: '$ 78,000', mode: 'positive' },
|
|
||||||
{ subject: 'Cash on Cash Return', value: '78%', mode: 'positive' },
|
|
||||||
{ subject: 'Selling Prise', value: '$500,000', mode: '' },
|
|
||||||
{ subject: 'Cash Out of Pocket', value: '$125,000', mode: '' },
|
|
||||||
{ subject: 'Annual Revenue', value: '$5,000', mode: '' },
|
|
||||||
{ subject: 'Annual Net Profit', value: '+$50,000', mode: 'extra_positive' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const AuthContext = React.createContext();
|
const AuthContext = React.createContext();
|
||||||
|
|
||||||
function AuthProvider({ children }) {
|
function AuthProvider({ children }) {
|
||||||
@@ -82,19 +25,14 @@ function AuthProvider({ children }) {
|
|||||||
authService.onAuthStateChanged((authUser) => {
|
authService.onAuthStateChanged((authUser) => {
|
||||||
dispatch(showMessage({ message: 'Signing...' }));
|
dispatch(showMessage({ message: 'Signing...' }));
|
||||||
if (authUser) {
|
if (authUser) {
|
||||||
const storageUser = JSON.parse(localStorage.user ?? '{}');
|
|
||||||
authService
|
authService
|
||||||
.getUserData(authUser.uid)
|
.getUserData(authUser.uid)
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
success({ ...user, ...storageUser }, 'Signed in');
|
success(user, 'Signed in');
|
||||||
} else {
|
} else {
|
||||||
// First login
|
|
||||||
const { displayName, photoURL, email } = authUser;
|
const { displayName, photoURL, email } = authUser;
|
||||||
success(
|
success({ role: 'user', data: { displayName, photoURL, email } }, 'Signed in');
|
||||||
{ role: 'user', data: { displayName, photoURL, email }, ...storageUser },
|
|
||||||
'Signed in'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
|||||||
@@ -1,3 +1,20 @@
|
|||||||
export { default as useWindowDimensions } from './useWindowDimensions';
|
import { useState, useEffect } from 'react';
|
||||||
export { default as useOnClickOutside } from './useOnClickOutside';
|
|
||||||
export { default as usePropertiesHeader } from './usePropertiesHeader';
|
export function useWindowDimensions() {
|
||||||
|
const getWindowDimensions = () => {
|
||||||
|
const { innerWidth: width, innerHeight: height } = window;
|
||||||
|
|
||||||
|
return { width, height };
|
||||||
|
};
|
||||||
|
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onRecize = () => setWindowDimensions(getWindowDimensions());
|
||||||
|
|
||||||
|
window.addEventListener('resize', onRecize);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('resize', onRecize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return windowDimensions;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function useOnClickOutside(ref, handler) {
|
|
||||||
useEffect(() => {
|
|
||||||
const onEvent = (event) => {
|
|
||||||
if (!ref.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event instanceof KeyboardEvent && event.key === 'Escape') {
|
|
||||||
handler(event);
|
|
||||||
} else if (ref.current.contains(event.target)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
handler(event);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousedown', onEvent);
|
|
||||||
document.addEventListener('touchstart', onEvent);
|
|
||||||
document.addEventListener('keydown', onEvent);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('mousedown', onEvent);
|
|
||||||
document.removeEventListener('touchstart', onEvent);
|
|
||||||
document.removeEventListener('keydown', onEvent);
|
|
||||||
};
|
|
||||||
}, [ref, handler]);
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { PROPERTIES_LAYOUTS } from 'app/configs/consts';
|
|
||||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function usePropertiesHeader(items) {
|
|
||||||
const [categories, setCategories] = useState([
|
|
||||||
{
|
|
||||||
name: 'all',
|
|
||||||
amount: 0,
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const [layouts, setLayouts] = useState([
|
|
||||||
{ name: PROPERTIES_LAYOUTS.list, active: true },
|
|
||||||
{ name: PROPERTIES_LAYOUTS.grid, active: false },
|
|
||||||
]);
|
|
||||||
|
|
||||||
const activeCategory = useMemo(
|
|
||||||
() => categories.find(({ active }) => active)?.name ?? categories[0].name,
|
|
||||||
[categories]
|
|
||||||
);
|
|
||||||
const activeLayout = useMemo(
|
|
||||||
() => layouts.find(({ active }) => active)?.name ?? PROPERTIES_LAYOUTS.list,
|
|
||||||
[layouts]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onCategory = useCallback(
|
|
||||||
(value) => {
|
|
||||||
if (value === activeCategory) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCategories((prevState) =>
|
|
||||||
prevState.map((category) => ({ ...category, active: category.name === value }))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[activeCategory]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onLayout = useCallback(
|
|
||||||
(value) => {
|
|
||||||
if (value === activeLayout) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setLayouts((prevState) => prevState.map(({ name }) => ({ name, active: name === value })));
|
|
||||||
},
|
|
||||||
[activeLayout]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onItemDelete = (itemCategory) => {
|
|
||||||
setCategories((prevState) => {
|
|
||||||
const isItemCategoryLast =
|
|
||||||
prevState.find((category) => category.name === itemCategory)?.amount === 1;
|
|
||||||
|
|
||||||
return prevState
|
|
||||||
.map((category, idx) => {
|
|
||||||
if (!idx) {
|
|
||||||
return {
|
|
||||||
name: category.name,
|
|
||||||
amount: category.amount - 1,
|
|
||||||
active: isItemCategoryLast,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (category?.name === itemCategory) {
|
|
||||||
if (category.amount > 1) {
|
|
||||||
return { ...category, amount: category.amount - 1 };
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return category;
|
|
||||||
})
|
|
||||||
.filter((category) => category);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let updatedCategories = [...categories];
|
|
||||||
items.forEach((item) => {
|
|
||||||
const hasItemCategory = updatedCategories.find((category) => category.name === item.category);
|
|
||||||
updatedCategories = updatedCategories.map((category, idx) => {
|
|
||||||
if (!idx) {
|
|
||||||
category.amount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return category;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (hasItemCategory) {
|
|
||||||
updatedCategories = updatedCategories.map((category) => ({
|
|
||||||
...category,
|
|
||||||
amount: item.category === category.name ? category.amount + 1 : category.amount,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
updatedCategories.push({ name: item.category, amount: 1, active: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
setCategories(updatedCategories);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return { categories, activeCategory, onCategory, layouts, activeLayout, onLayout, onItemDelete };
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { useState, useEffect } from 'react';
|
|
||||||
|
|
||||||
export default function useWindowDimensions() {
|
|
||||||
const getWindowDimensions = () => {
|
|
||||||
const { innerWidth: width, innerHeight: height } = window;
|
|
||||||
|
|
||||||
return { width, height };
|
|
||||||
};
|
|
||||||
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions());
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onRecize = () => setWindowDimensions(getWindowDimensions());
|
|
||||||
|
|
||||||
window.addEventListener('resize', onRecize);
|
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', onRecize);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return windowDimensions;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import ForgotPasswordPage from './ForgotPasswordPage';
|
import ForgotPasswordPage from './ForgotPasswordPage';
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
import en from './i18n/en';
|
import en from './i18n/en';
|
||||||
|
|
||||||
i18next.addResourceBundle('en', 'forgotPasswordPage', en);
|
i18next.addResourceBundle('en', 'forgotPasswordPage', en);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import SignInPage from './SignInPage';
|
import SignInPage from './SignInPage';
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
import en from './i18n/en';
|
import en from './i18n/en';
|
||||||
|
|
||||||
i18next.addResourceBundle('en', 'signInPage', en);
|
i18next.addResourceBundle('en', 'signInPage', en);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import SignUpPage from './SignUpPage';
|
import SignUpPage from './SignUpPage';
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
import en from './i18n/en';
|
import en from './i18n/en';
|
||||||
|
|
||||||
i18next.addResourceBundle('en', 'signUpPage', en);
|
i18next.addResourceBundle('en', 'signUpPage', en);
|
||||||
|
|||||||
@@ -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-20"
|
className="rounded-[20px]"
|
||||||
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-20">
|
<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>
|
<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>
|
||||||
@@ -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/search"
|
to="/rent-and-buy"
|
||||||
>
|
>
|
||||||
{t('research_btn')}
|
{t('research_btn')}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -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-20 shadow-light">
|
<article className="flex flex-col justify-between max-w-[460px] w-full h-[526px] bg-primary-light rounded-[20px] shadow-light">
|
||||||
<div>
|
<div>
|
||||||
<img
|
<img
|
||||||
className="w-full h-[230px] mb-20 rounded-20 object-cover"
|
className="w-full h-[230px] mb-20 rounded-[20px] object-cover"
|
||||||
src={image}
|
src={image}
|
||||||
alt={title}
|
alt={title}
|
||||||
width="460"
|
width="460"
|
||||||
|
|||||||
@@ -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-20"
|
className="grid grid-cols-2 gap-x-20 gap-y-32 px-80 py-40 bg-primary-light rounded-[20px]"
|
||||||
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">
|
||||||
|
|||||||
@@ -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-20 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-[20px] 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>
|
||||||
|
|||||||
@@ -1,25 +1,19 @@
|
|||||||
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 = () => {
|
||||||
const trimmedQuery = query.trim();
|
// query
|
||||||
if (!trimmedQuery) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigate(`/rent-and-buy/search?query=${trimmedQuery}`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -32,7 +26,6 @@ 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')}
|
||||||
|
|||||||
@@ -1,43 +1,7 @@
|
|||||||
import FusePageSimple from '@fuse/core/FusePageSimple';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { useState } from 'react';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withTranslation } from 'react-i18next';
|
import FusePageSimple from '@fuse/core/FusePageSimple';
|
||||||
import SearchInput from '../../shared-components/SearchInput';
|
import DemoContent from '@fuse/core/DemoContent';
|
||||||
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': {
|
||||||
@@ -52,39 +16,21 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
|
|||||||
'& .FusePageSimple-sidebarContent': {},
|
'& .FusePageSimple-sidebarContent': {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function DashboardPage({ t }) {
|
function DashboardPage(props) {
|
||||||
const [query, setQuery] = useState('');
|
const { t } = useTranslation('dashboardPage');
|
||||||
|
|
||||||
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="w-full p-60">
|
<div className="p-24">
|
||||||
<div className="flex flex-wrap justify-center items-center gap-20 mb-52">
|
<h4>Content</h4>
|
||||||
{categoriesMock.map(({ title, value, valueColor }) => (
|
<br />
|
||||||
<DashboardCategory title={title} value={value} valueColor={valueColor} />
|
<DemoContent />
|
||||||
))}
|
|
||||||
</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"
|
||||||
@@ -92,4 +38,4 @@ function DashboardPage({ t }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTranslation('dashboardPage')(DashboardPage);
|
export default DashboardPage;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
import Dashboard from './Dashboard';
|
import Dashboard from './Dashboard';
|
||||||
import en from './i18n/en';
|
import en from './i18n/en';
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
const locale = {
|
const locale = {};
|
||||||
search_input_btn: 'calculate',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default locale;
|
export default locale;
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
import FusePageSimple from '@fuse/core/FusePageSimple';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { PROPERTIES_LAYOUTS } from 'app/configs/consts';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { selectUserFavorites, updateUserFavorites } from 'app/store/userSlice';
|
import FusePageSimple from '@fuse/core/FusePageSimple';
|
||||||
import clsx from 'clsx';
|
import DemoContent from '@fuse/core/DemoContent';
|
||||||
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': {
|
||||||
@@ -24,73 +16,21 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
|
|||||||
'& .FusePageSimple-sidebarContent': {},
|
'& .FusePageSimple-sidebarContent': {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function FavoritesPage() {
|
function FavoritesPage(props) {
|
||||||
const dispatch = useDispatch();
|
const { t } = useTranslation('favoritesPage');
|
||||||
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="w-full p-60">
|
<div className="p-24">
|
||||||
<Paper className="w-full h-320 mb-[30px] rounded-20 shadow-light" />
|
<h4>Content</h4>
|
||||||
<PropertiesHeader
|
<br />
|
||||||
className="mb-40"
|
<DemoContent />
|
||||||
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"
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { lazy } from 'react';
|
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
|
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: {
|
||||||
@@ -24,3 +22,28 @@ 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;
|
||||||
|
*/
|
||||||
|
|||||||
@@ -1,15 +1,7 @@
|
|||||||
import FusePageSimple from '@fuse/core/FusePageSimple';
|
|
||||||
import Paper from '@mui/material/Paper';
|
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import { PROPERTIES_LAYOUTS } from 'app/configs/consts';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { selectUserHistory, updateUserFavorites, updateUserHistory } from 'app/store/userSlice';
|
import FusePageSimple from '@fuse/core/FusePageSimple';
|
||||||
import clsx from 'clsx';
|
import DemoContent from '@fuse/core/DemoContent';
|
||||||
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': {
|
||||||
@@ -24,83 +16,21 @@ const Root = styled(FusePageSimple)(({ theme }) => ({
|
|||||||
'& .FusePageSimple-sidebarContent': {},
|
'& .FusePageSimple-sidebarContent': {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function HistoryPage() {
|
function HistoryPage(props) {
|
||||||
const dispatch = useDispatch();
|
const { t } = useTranslation('historyPage');
|
||||||
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="w-full p-60">
|
<div className="p-24">
|
||||||
<Paper className="w-full h-320 mb-[30px] rounded-20 shadow-light" />
|
<h4>Content</h4>
|
||||||
<PropertiesHeader
|
<br />
|
||||||
className="mb-40"
|
<DemoContent />
|
||||||
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"
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { lazy } from 'react';
|
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
|
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: {
|
||||||
@@ -24,3 +22,28 @@ 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;
|
||||||
|
*/
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ 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]) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { lazy } from 'react';
|
import { lazy } from 'react';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import { authRoles } from '../../../configs/consts';
|
import authRoles from '../../../configs/authRoles';
|
||||||
import en from './i18n/en';
|
import en from './i18n/en';
|
||||||
|
|
||||||
i18next.addResourceBundle('en', 'profilePage', en);
|
i18next.addResourceBundle('en', 'profilePage', en);
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const locale = {
|
|
||||||
title: 'Rent&Buy Analysis',
|
|
||||||
show_btn: 'show',
|
|
||||||
see_more_btn: 'See more',
|
|
||||||
view: 'View the Calculation',
|
|
||||||
};
|
|
||||||
|
|
||||||
export default locale;
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Paper from '@mui/material/Paper';
|
|
||||||
|
|
||||||
function PropertyAnalysisHeader() {
|
|
||||||
return (
|
|
||||||
<Paper>
|
|
||||||
<div></div>
|
|
||||||
</Paper>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PropertyAnalysisHeader;
|
|
||||||
@@ -1,14 +1,7 @@
|
|||||||
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';
|
import { forwardRef, memo, useCallback } from 'react';
|
||||||
|
import TextField from '@mui/material/TextField';
|
||||||
const SEARCH_INPUT_MODES = {
|
import Button from '@mui/material/Button';
|
||||||
simple: 'simple',
|
import _ from '@lodash';
|
||||||
manual: 'manual',
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledTextField = forwardRef((props, ref) => (
|
const StyledTextField = forwardRef((props, ref) => (
|
||||||
<TextField
|
<TextField
|
||||||
@@ -26,32 +19,28 @@ const StyledTextField = forwardRef((props, ref) => (
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
function SearchInput({ className, mode, placeholder, btnText, query, onType, onSearch }) {
|
function SearchInput({ mode, placeholder, btnText, query, onType, onSearch }) {
|
||||||
const isSimpleMode = mode === SEARCH_INPUT_MODES.simple;
|
const isSimpleMode = mode === '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={clsx('flex items-center gap-20', className)}>
|
<form className="flex items-center gap-20">
|
||||||
<StyledTextField
|
<StyledTextField
|
||||||
type="text"
|
type="text"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
className="w-full bourder-0"
|
className="max-w-[620px] w-full bourder-0"
|
||||||
defaultValue={query}
|
defaultValue={query}
|
||||||
placeholder={placeholder ?? ''}
|
placeholder={placeholder}
|
||||||
onChange={debouncedOnType}
|
onChange={debouncedOnType}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* {isManualMode && <Select />} */}
|
{isSimpleMode && (
|
||||||
|
|
||||||
{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}
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
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);
|
|
||||||
@@ -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 }, favorites: [] };
|
const value = { role: 'user', data: { displayName, email } };
|
||||||
|
|
||||||
return firebaseDb.set(userRef, value);
|
return firebaseDb.set(userRef, value);
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
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',
|
||||||
@@ -14,15 +12,7 @@ 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, propertyService };
|
export { authService, firebase };
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +1,35 @@
|
|||||||
import browserHistory from '@history';
|
/* eslint import/no-extraneous-dependencies: off */
|
||||||
import _ from '@lodash';
|
|
||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||||
import settingsConfig from 'app/configs/settingsConfig';
|
import history from '@history';
|
||||||
import { showMessage } from 'app/store/fuse/messageSlice';
|
import _ from '@lodash';
|
||||||
import { setInitialSettings } from 'app/store/fuse/settingsSlice';
|
import { setInitialSettings } from 'app/store/fuse/settingsSlice';
|
||||||
|
import { showMessage } from 'app/store/fuse/messageSlice';
|
||||||
|
import settingsConfig from 'app/configs/settingsConfig';
|
||||||
import { authService } from '../services';
|
import { authService } from '../services';
|
||||||
|
|
||||||
export const setUser = createAsyncThunk('user/setUser', async (user) => {
|
export const setUser = createAsyncThunk('user/setUser', async (user, { dispatch, getState }) => {
|
||||||
// You can redirect the logged-in user to a specific route depending on his role
|
/*
|
||||||
|
You can redirect the logged-in user to a specific route depending on his role
|
||||||
|
*/
|
||||||
if (user.loginRedirectUrl) {
|
if (user.loginRedirectUrl) {
|
||||||
settingsConfig.loginRedirectUrl = user.loginRedirectUrl; // for example 'apps/academy'
|
settingsConfig.loginRedirectUrl = user.loginRedirectUrl; // for example 'apps/academy'
|
||||||
}
|
}
|
||||||
|
|
||||||
return _.merge({}, initialState, user);
|
return user;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateUserSettings = createAsyncThunk(
|
export const updateUserSettings = createAsyncThunk(
|
||||||
'user/updateSettings',
|
'user/updateSettings',
|
||||||
async (settings, { dispatch, getState }) => {
|
async (settings, { dispatch, getState }) => {
|
||||||
const { user } = getState();
|
const { user } = getState();
|
||||||
const newUser = _.omit(_.merge({}, user, { data: { ...settings } }), 'history');
|
const newUser = _.merge({}, user, { data: { ...settings } });
|
||||||
|
|
||||||
dispatch(updateUserData(newUser))
|
dispatch(updateUserData(newUser));
|
||||||
.then(() => {
|
|
||||||
dispatch(showMessage({ message: 'User data saved' }));
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
dispatch(showMessage({ message: error.message }));
|
|
||||||
});
|
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const updateUserFavorites = createAsyncThunk(
|
|
||||||
'user/updateFavorites',
|
|
||||||
async (item, { dispatch, getState }) => {
|
|
||||||
const { user } = getState();
|
|
||||||
const hasItemInFavorites = user.favorites.find(
|
|
||||||
(favoriteItem) => favoriteItem.id === item.id && item.favorite
|
|
||||||
);
|
|
||||||
const hasItemInHistory = user.history.find((history) => history.id === item.id);
|
|
||||||
|
|
||||||
const favorites = hasItemInFavorites
|
|
||||||
? user.favorites.filter((favorite) => favorite.id !== item.id)
|
|
||||||
: [...user.favorites, { ...item, favorite: true }];
|
|
||||||
|
|
||||||
if (hasItemInHistory) {
|
|
||||||
const history = user.history.map((historyItem) => {
|
|
||||||
if (historyItem.id === item.id) {
|
|
||||||
return { ...historyItem, favorite: !hasItemInFavorites };
|
|
||||||
}
|
|
||||||
|
|
||||||
return historyItem;
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(updateUserHistory(history));
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUserData = _.omit({ ...user, favorites }, 'history');
|
|
||||||
|
|
||||||
dispatch(updateUserData(newUserData))
|
|
||||||
.then(() => {
|
|
||||||
if (hasItemInFavorites) {
|
|
||||||
dispatch(showMessage({ message: 'The property is removed from favorites' }));
|
|
||||||
} else {
|
|
||||||
dispatch(showMessage({ message: 'The property is saved to favorites' }));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
dispatch(showMessage({ message: error.message }));
|
|
||||||
});
|
|
||||||
|
|
||||||
return favorites;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
export const updateUserHistory = (history) => (dispatch) => {
|
|
||||||
localStorage.setItem('user', JSON.stringify({ history }));
|
|
||||||
dispatch(userHistoryUpdated(history));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const logoutUser = () => async (dispatch, getState) => {
|
export const logoutUser = () => async (dispatch, getState) => {
|
||||||
const { user } = getState();
|
const { user } = getState();
|
||||||
|
|
||||||
@@ -89,7 +38,7 @@ export const logoutUser = () => async (dispatch, getState) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
browserHistory.push({
|
history.push({
|
||||||
pathname: '/',
|
pathname: '/',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -104,8 +53,14 @@ export const updateUserData = (user) => async (dispatch, getState) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
authService
|
||||||
return authService.updateUserData(user);
|
.updateUserData(user)
|
||||||
|
.then(() => {
|
||||||
|
dispatch(showMessage({ message: 'User data saved' }));
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
dispatch(showMessage({ message: error.message }));
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@@ -114,8 +69,6 @@ const initialState = {
|
|||||||
displayName: 'John Doe',
|
displayName: 'John Doe',
|
||||||
email: 'johndoe@withinpixels.com',
|
email: 'johndoe@withinpixels.com',
|
||||||
},
|
},
|
||||||
history: [],
|
|
||||||
favorites: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const userSlice = createSlice({
|
const userSlice = createSlice({
|
||||||
@@ -123,21 +76,15 @@ const userSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
userLoggedOut: (state, action) => initialState,
|
userLoggedOut: (state, action) => initialState,
|
||||||
userHistoryUpdated: (state, action) => ({ ...state, history: action.payload }),
|
|
||||||
},
|
},
|
||||||
extraReducers: {
|
extraReducers: {
|
||||||
[updateUserSettings.fulfilled]: (state, action) => action.payload,
|
[updateUserSettings.fulfilled]: (state, action) => action.payload,
|
||||||
[updateUserFavorites.fulfilled]: (state, action) => ({ ...state, favorites: action.payload }),
|
|
||||||
[setUser.fulfilled]: (state, action) => action.payload,
|
[setUser.fulfilled]: (state, action) => action.payload,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { userLoggedOut, userHistoryUpdated } = userSlice.actions;
|
export const { userLoggedOut } = userSlice.actions;
|
||||||
|
|
||||||
export const selectUser = ({ user }) => user;
|
export const selectUser = ({ user }) => user;
|
||||||
|
|
||||||
export const selectUserHistory = ({ user }) => user.history;
|
|
||||||
|
|
||||||
export const selectUserFavorites = ({ user }) => user.favorites;
|
|
||||||
|
|
||||||
export default userSlice.reducer;
|
export default userSlice.reducer;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const config = {
|
|||||||
title: 'Layout 1 - Dashboard',
|
title: 'Layout 1 - Dashboard',
|
||||||
defaults: {
|
defaults: {
|
||||||
mode: 'container',
|
mode: 'container',
|
||||||
containerWidth: 1590,
|
containerWidth: 1570,
|
||||||
navbar: {
|
navbar: {
|
||||||
display: true,
|
display: true,
|
||||||
folded: true,
|
folded: true,
|
||||||
|
|||||||
@@ -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 = 330;
|
const navbarWidth = 280;
|
||||||
|
|
||||||
const StyledNavBar = styled('div')(({ theme, open, position }) => ({
|
const StyledNavBar = styled('div')(({ theme, open, position }) => ({
|
||||||
minWidth: navbarWidth,
|
minWidth: navbarWidth,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function FooterLayout2() {
|
|||||||
const { t } = useTranslation('layout2');
|
const { t } = useTranslation('layout2');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="z-[10000] flex items-center justify-center w-full bg-common-layout">
|
<footer className="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>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function HeaderLayout2(props) {
|
|||||||
const { t } = useTranslation('layout2');
|
const { t } = useTranslation('layout2');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="fixed z-[10000] flex items-center justify-center w-full h-72 px-10 bg-primary-light">
|
<header className="fixed z-50 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
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ function NavLinks({ className }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link className={className} to="/rent-and-buy/search">
|
<Link className={className} to="/rent-and-buy">
|
||||||
{t('rent_and_buy')}
|
{t('rent_and_buy')}
|
||||||
</Link>
|
</Link>
|
||||||
<Link className={className} to={{ hash: 'about-us' }}>
|
<Link className={className} to={{ hash: 'about-us' }}>
|
||||||
|
|||||||
@@ -11,11 +11,10 @@
|
|||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
/* html and body tweaks */
|
/* html and body tweaks */
|
||||||
html,
|
html, body {
|
||||||
body {
|
|
||||||
height: auto !important;
|
height: auto !important;
|
||||||
overflow: initial !important;
|
overflow: initial !important;
|
||||||
background: none;
|
background: none
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Page breaks */
|
/* Page breaks */
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
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;
|
||||||
@@ -18,31 +18,32 @@ pre[class*='language-'] {
|
|||||||
hyphens: none;
|
hyphens: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
code[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 {
|
code[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,
|
||||||
@@ -51,7 +52,7 @@ pre[class*='language-'] {
|
|||||||
color: #fd9170;
|
color: #fd9170;
|
||||||
}
|
}
|
||||||
|
|
||||||
[class*='language-'] .namespace {
|
[class*="language-"] .namespace {
|
||||||
opacity: 0.7;
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,8 +186,6 @@ module.exports = {
|
|||||||
primary: '#151B30',
|
primary: '#151B30',
|
||||||
secondary: '#6D6D6D',
|
secondary: '#6D6D6D',
|
||||||
disabled: '#D9D9D9',
|
disabled: '#D9D9D9',
|
||||||
highlight1: '#FFBC6E',
|
|
||||||
highlight2: '#DB00FF',
|
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
light: '#FFFFFF',
|
light: '#FFFFFF',
|
||||||
|
|||||||
Reference in New Issue
Block a user