diff --git a/src/app/configs/authRoles.js b/src/app/configs/authRoles.js deleted file mode 100644 index 5159ed3..0000000 --- a/src/app/configs/authRoles.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Authorization Roles - */ -const authRoles = { - admin: ['admin'], - staff: ['admin', 'staff'], - user: ['admin', 'staff', 'user'], - onlyGuest: [], -}; - -export default authRoles; diff --git a/src/app/configs/consts.js b/src/app/configs/consts.js new file mode 100644 index 0000000..4755ddf --- /dev/null +++ b/src/app/configs/consts.js @@ -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: [], +}; diff --git a/src/app/contexts/AuthContext.js b/src/app/contexts/AuthContext.js index 7cf9d2f..5718efe 100644 --- a/src/app/contexts/AuthContext.js +++ b/src/app/contexts/AuthContext.js @@ -6,6 +6,63 @@ import { showMessage } from 'app/store/fuse/messageSlice'; import { logoutUser, setUser } from 'app/store/userSlice'; 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(); function AuthProvider({ children }) { @@ -25,14 +82,19 @@ function AuthProvider({ children }) { authService.onAuthStateChanged((authUser) => { dispatch(showMessage({ message: 'Signing...' })); if (authUser) { + const storageUser = JSON.parse(localStorage.user ?? '{}'); authService .getUserData(authUser.uid) .then((user) => { if (user) { - success(user, 'Signed in'); + success({ ...user, ...storageUser }, 'Signed in'); } else { + // First login const { displayName, photoURL, email } = authUser; - success({ role: 'user', data: { displayName, photoURL, email } }, 'Signed in'); + success( + { role: 'user', data: { displayName, photoURL, email }, ...storageUser }, + 'Signed in' + ); } }) .catch((error) => { diff --git a/src/app/hooks/index.js b/src/app/hooks/index.js index 68e6a53..4e6ba13 100644 --- a/src/app/hooks/index.js +++ b/src/app/hooks/index.js @@ -1,20 +1,3 @@ -import { useState, useEffect } from 'react'; - -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; -} +export { default as useWindowDimensions } from './useWindowDimensions'; +export { default as useOnClickOutside } from './useOnClickOutside'; +export { default as usePropertiesHeader } from './usePropertiesHeader'; diff --git a/src/app/hooks/useOnClickOutside.js b/src/app/hooks/useOnClickOutside.js new file mode 100644 index 0000000..98cc268 --- /dev/null +++ b/src/app/hooks/useOnClickOutside.js @@ -0,0 +1,29 @@ +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]); +} diff --git a/src/app/hooks/usePropertiesHeader.js b/src/app/hooks/usePropertiesHeader.js new file mode 100644 index 0000000..6e3d4d1 --- /dev/null +++ b/src/app/hooks/usePropertiesHeader.js @@ -0,0 +1,105 @@ +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 }; +} diff --git a/src/app/hooks/useWindowDimensions.js b/src/app/hooks/useWindowDimensions.js new file mode 100644 index 0000000..8991ab0 --- /dev/null +++ b/src/app/hooks/useWindowDimensions.js @@ -0,0 +1,20 @@ +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; +} diff --git a/src/app/main/authPages/forgot-password/ForgotPasswordConfig.js b/src/app/main/authPages/forgot-password/ForgotPasswordConfig.js index 338b1f6..7afb9f9 100644 --- a/src/app/main/authPages/forgot-password/ForgotPasswordConfig.js +++ b/src/app/main/authPages/forgot-password/ForgotPasswordConfig.js @@ -1,7 +1,7 @@ import i18next from 'i18next'; import ForgotPasswordPage from './ForgotPasswordPage'; -import authRoles from '../../../configs/authRoles'; +import authRoles from '../../../configs/consts'; import en from './i18n/en'; i18next.addResourceBundle('en', 'forgotPasswordPage', en); diff --git a/src/app/main/authPages/sign-in/SignInConfig.js b/src/app/main/authPages/sign-in/SignInConfig.js index 8f48632..9eb269a 100644 --- a/src/app/main/authPages/sign-in/SignInConfig.js +++ b/src/app/main/authPages/sign-in/SignInConfig.js @@ -1,7 +1,7 @@ import i18next from 'i18next'; import SignInPage from './SignInPage'; -import authRoles from '../../../configs/authRoles'; +import authRoles from '../../../configs/consts'; import en from './i18n/en'; i18next.addResourceBundle('en', 'signInPage', en); diff --git a/src/app/main/authPages/sign-up/SignUpConfig.js b/src/app/main/authPages/sign-up/SignUpConfig.js index 1fdf098..052a66a 100644 --- a/src/app/main/authPages/sign-up/SignUpConfig.js +++ b/src/app/main/authPages/sign-up/SignUpConfig.js @@ -1,7 +1,7 @@ import i18next from 'i18next'; import SignUpPage from './SignUpPage'; -import authRoles from '../../../configs/authRoles'; +import authRoles from '../../../configs/consts'; import en from './i18n/en'; i18next.addResourceBundle('en', 'signUpPage', en); diff --git a/src/app/main/home/components/AboutUs.js b/src/app/main/home/components/AboutUs.js index 89bf520..42ade72 100644 --- a/src/app/main/home/components/AboutUs.js +++ b/src/app/main/home/components/AboutUs.js @@ -9,7 +9,7 @@ function AboutUs({ t }) {