diff --git a/src/common/constants/company-settings.ts b/src/common/constants/company-settings.ts index a5e9f5e356..d67df929c9 100644 --- a/src/common/constants/company-settings.ts +++ b/src/common/constants/company-settings.ts @@ -9,7 +9,9 @@ */ import DefaultLogo from '../../resources/images/invoiceninja-logo@light.png'; +import SmallLogo from 'public/logo180.png'; export default { logo: DefaultLogo, + smallLogo: SmallLogo, }; diff --git a/src/common/hooks/useHandleCollapseExpandSidebar.ts b/src/common/hooks/useHandleCollapseExpandSidebar.ts new file mode 100644 index 0000000000..4bdadba196 --- /dev/null +++ b/src/common/hooks/useHandleCollapseExpandSidebar.ts @@ -0,0 +1,37 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { useUpdateCompanyUser } from '$app/pages/settings/user/common/hooks/useUpdateCompanyUser'; +import { cloneDeep, set } from 'lodash'; +import { useHandleCurrentUserChangeProperty } from './useHandleCurrentUserChange'; +import { useInjectUserChanges } from './useInjectUserChanges'; + +export function useHandleCollapseExpandSidebar() { + const userChanges = useInjectUserChanges(); + + const updateCompanyUser = useUpdateCompanyUser(); + const handleUserChange = useHandleCurrentUserChangeProperty(); + + return (value: boolean) => { + handleUserChange('company_user.react_settings.show_mini_sidebar', value); + + if (userChanges) { + const updatedUserChanges = cloneDeep(userChanges); + + set( + updatedUserChanges, + 'company_user.react_settings.show_mini_sidebar', + value + ); + + updateCompanyUser(updatedUserChanges); + } + }; +} diff --git a/src/common/hooks/useInjectUserChanges.ts b/src/common/hooks/useInjectUserChanges.ts index 2f83cb710d..1bb3ab841e 100644 --- a/src/common/hooks/useInjectUserChanges.ts +++ b/src/common/hooks/useInjectUserChanges.ts @@ -31,7 +31,7 @@ export function useInjectUserChanges(options?: Options) { const changes = useUserChanges(); useEffect(() => { - if (changes && options?.overwrite === false) { + if (Object.keys(changes || {}).length && !options?.overwrite) { // We don't want to overwrite existing changes, // so let's just not inject anything if we already have a value, // and relative argument. diff --git a/src/common/hooks/useLogo.ts b/src/common/hooks/useLogo.ts index f6d8740a89..2a13f4c60a 100644 --- a/src/common/hooks/useLogo.ts +++ b/src/common/hooks/useLogo.ts @@ -13,15 +13,21 @@ import { useCompanyChanges } from './useCompanyChanges'; import { useCurrentCompany } from './useCurrentCompany'; import { useTranslation } from 'react-i18next'; -export function useLogo() { +interface Params { + fallbackSmallLogo?: boolean; +} + +export function useLogo(props?: Params) { + const { fallbackSmallLogo } = props || {}; + const companyChanges = useCompanyChanges(); const currentCompany = useCurrentCompany(); - return ( - companyChanges?.settings?.company_logo || + return companyChanges?.settings?.company_logo || currentCompany?.settings?.company_logo || - companySettings.logo - ); + fallbackSmallLogo + ? companySettings.smallLogo + : companySettings.logo; } export function useCompanyName() { diff --git a/src/common/interfaces/company.interface.ts b/src/common/interfaces/company.interface.ts index 5842494787..009e539623 100644 --- a/src/common/interfaces/company.interface.ts +++ b/src/common/interfaces/company.interface.ts @@ -337,6 +337,8 @@ export interface Settings { task_round_to_nearest: number; merge_e_invoice_to_pdf: boolean; payment_flow: string; + enable_client_profile_update: boolean; + preference_product_notes_for_html_view: boolean; } export interface TaxData { diff --git a/src/components/CompanySwitcher.tsx b/src/components/CompanySwitcher.tsx index 4451d0b0fe..d11ddbe167 100644 --- a/src/components/CompanySwitcher.tsx +++ b/src/components/CompanySwitcher.tsx @@ -10,27 +10,37 @@ import { Menu, Transition } from '@headlessui/react'; import { AuthenticationTypes } from '$app/common/dtos/authentication'; -import { useCurrentUser } from '$app/common/hooks/useCurrentUser'; import { authenticate } from '$app/common/stores/slices/user'; import { RootState } from '$app/common/stores/store'; import { Fragment, useEffect, useState } from 'react'; -import { Check, ChevronDown } from 'react-feather'; import { useTranslation } from 'react-i18next'; import { useQueryClient } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; -import { DropdownElement } from './dropdown/DropdownElement'; import { useLogo } from '$app/common/hooks/useLogo'; import { useCompanyName } from '$app/common/hooks/useLogo'; import { CompanyCreate } from '$app/pages/settings/company/create/CompanyCreate'; import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; import { isDemo, isHosted, isSelfHosted } from '$app/common/helpers'; import { freePlan } from '$app/common/guards/guards/free-plan'; -import { Icon } from './icons/Icon'; -import { MdLogout, MdManageAccounts } from 'react-icons/md'; -import { BiPlusCircle } from 'react-icons/bi'; -import { useColorScheme } from '$app/common/colors'; import { useAdmin } from '$app/common/hooks/permissions/useHasPermission'; import { useLocation, useNavigate, useParams } from 'react-router-dom'; +import { ExpandCollapseChevron } from './icons/ExpandCollapseChevron'; +import { styled } from 'styled-components'; +import { usePreventNavigation } from '$app/common/hooks/usePreventNavigation'; +import { Check } from './icons/Check'; +import { Plus } from './icons/Plus'; +import { Person } from './icons/Person'; +import { Exit } from './icons/Exit'; +import { useCurrentUser } from '$app/common/hooks/useCurrentUser'; +import { useInjectUserChanges } from '$app/common/hooks/useInjectUserChanges'; +import { useColorScheme } from '$app/common/colors'; +import companySettings from '$app/common/constants/company-settings'; + +const SwitcherDiv = styled.div` + &:hover { + background-color: ${(props) => props.theme.hoverColor}; + } +`; export function CompanySwitcher() { const [t] = useTranslation(); @@ -43,8 +53,7 @@ export function CompanySwitcher() { const { id } = useParams(); const canUserAddCompany = isSelfHosted() || (isHosted() && !freePlan()); - const logo = useLogo(); - const user = useCurrentUser(); + const logo = useLogo({ fallbackSmallLogo: true }); const location = useLocation(); const colors = useColorScheme(); const companyName = useCompanyName(); @@ -52,6 +61,15 @@ export function CompanySwitcher() { const { isAdmin, isOwner } = useAdmin(); const currentCompany = useCurrentCompany(); + const currentUser = useCurrentUser(); + const userChanges = useInjectUserChanges(); + + const isMiniSidebar = Boolean( + userChanges?.company_user?.react_settings?.show_mini_sidebar + ); + + const preventNavigation = usePreventNavigation(); + const [shouldShowAddCompany, setShouldShowAddCompany] = useState(false); const [isCompanyCreateModalOpened, setIsCompanyCreateModalOpened] = @@ -97,6 +115,22 @@ export function CompanySwitcher() { } }, [currentCompany]); + if (isMiniSidebar) { + return ( + <> + Company logo + + ); + } + return ( <> - -
- Company logo -
- - {companyName} - - {(user?.first_name || user?.last_name) && ( - - {user.first_name} {user.last_name} - - )} -
+ +
+ Company logo + + + {companyName} + + +
-
-
+
- -

{t('signed_in_as')}

-

{user?.email}

-
+
+

{t('signed_in_as')}

+ +

+ {currentUser?.email} +

+
-
+
{state?.api?.length >= 1 && state?.api?.map((record: any, index: number) => ( - switchCompany(index)} - > -
- - {record.company.settings.name || - t('untitled_company')} - - - {state.currentIndex === index && } -
-
+
+ {index === 0 && ( +

+ {t('company')} +

+ )} + + + preventNavigation({ + fn: () => switchCompany(index), + actionKey: 'switchCompany', + }) + } + > +
+ Company logo + +
+ {record.company.settings.name || + t('untitled_company')} +
+
+ + {state.currentIndex === index && ( + + )} +
+
))}
+
{shouldShowAddCompany && canUserAddCompany && (isAdmin || isOwner) && ( - setIsCompanyCreateModalOpened(true)} - icon={} - > - {t('add_company')} - +
+ setIsCompanyCreateModalOpened(true)} + > + + + {t('add_company')} + +
)} {(isAdmin || isOwner) && ( - } - > - {t('account_management')} - +
+ + preventNavigation({ + url: '/settings/account_management', + }) + } + > + + + {t('account_management')} + +
)} - } - > - {t('logout')} - +
+ + preventNavigation({ + url: '/logout', + }) + } + > + + + {t('logout')} + +
diff --git a/src/components/HelpSidebarIcons.tsx b/src/components/HelpSidebarIcons.tsx index 9114b534c8..517aa1cc35 100644 --- a/src/components/HelpSidebarIcons.tsx +++ b/src/components/HelpSidebarIcons.tsx @@ -14,15 +14,13 @@ import { request } from '$app/common/helpers/request'; import { useCurrentAccount } from '$app/common/hooks/useCurrentAccount'; import { updateCompanyUsers } from '$app/common/stores/slices/company-users'; import { useFormik } from 'formik'; -import { useEffect, useState } from 'react'; +import { useState } from 'react'; import { HelpCircle, Info, Mail, MessageSquare, AlertCircle, - ChevronLeft, - ChevronRight, } from 'react-feather'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; @@ -32,9 +30,6 @@ import { Modal } from './Modal'; import { toast } from '$app/common/helpers/toast/toast'; import { useColorScheme } from '$app/common/colors'; import { useInjectUserChanges } from '$app/common/hooks/useInjectUserChanges'; -import { useHandleCurrentUserChangeProperty } from '$app/common/hooks/useHandleCurrentUserChange'; -import { useUpdateCompanyUser } from '$app/pages/settings/user/common/hooks/useUpdateCompanyUser'; -import { useCurrentUser } from '$app/common/hooks/useCurrentUser'; import classNames from 'classnames'; import { AboutModal } from './AboutModal'; import { Icon } from './icons/Icon'; @@ -43,6 +38,9 @@ import { useQuery } from 'react-query'; import axios from 'axios'; import { MdWarning } from 'react-icons/md'; import { UpdateAppModal } from './UpdateAppModal'; +import { OpenNavbarArrow } from './icons/OpenNavbarArrow'; +import { useHandleCollapseExpandSidebar } from '$app/common/hooks/useHandleCollapseExpandSidebar'; +import { CloseNavbarArrow } from './icons/CloseNavbarArrow'; interface Props { docsLink?: string; @@ -55,13 +53,11 @@ export function HelpSidebarIcons(props: Props) { const colors = useColorScheme(); const user = useInjectUserChanges(); const account = useCurrentAccount(); - const currentUser = useCurrentUser(); const { mobileNavbar } = props; const dispatch = useDispatch(); - const updateCompanyUser = useUpdateCompanyUser(); - const handleUserChange = useHandleCurrentUserChangeProperty(); + const handleCollapseExpandSidebar = useHandleCollapseExpandSidebar(); const { data: latestVersion } = useQuery({ queryKey: ['/pdf.invoicing.co/api/version'], @@ -129,20 +125,6 @@ export function HelpSidebarIcons(props: Props) { }); }; - useEffect(() => { - const showMiniSidebar = - user?.company_user?.react_settings?.show_mini_sidebar; - - if ( - user && - typeof showMiniSidebar !== 'undefined' && - currentUser?.company_user?.react_settings?.show_mini_sidebar !== - showMiniSidebar - ) { - updateCompanyUser(user); - } - }, [user?.company_user?.react_settings.show_mini_sidebar]); - return ( <>
- +
@@ -256,7 +238,7 @@ export function HelpSidebarIcons(props: Props) { content={t('error')} className="text-white rounded text-xs mb-2" > - + )} @@ -272,7 +254,7 @@ export function HelpSidebarIcons(props: Props) { className="cursor-pointer" onClick={() => setIsContactVisible(true)} > - +
) : (
- +
)} @@ -298,7 +280,7 @@ export function HelpSidebarIcons(props: Props) { content={t('support_forum')} className="text-white rounded text-xs mb-2" > - + @@ -317,7 +299,7 @@ export function HelpSidebarIcons(props: Props) { content={t('user_guide')} className="text-white rounded text-xs mb-2" > - + @@ -330,20 +312,15 @@ export function HelpSidebarIcons(props: Props) { content={t('about')} className="text-white rounded text-xs mb-2" > - + )} - +
); diff --git a/src/components/icons/Check.tsx b/src/components/icons/Check.tsx new file mode 100644 index 0000000000..f16c301b31 --- /dev/null +++ b/src/components/icons/Check.tsx @@ -0,0 +1,36 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +type Props = { + color?: string; +}; + +export function Check({ color = '#18181B' }: Props) { + return ( + + + + ); +} diff --git a/src/components/icons/CloseNavbarArrow.tsx b/src/components/icons/CloseNavbarArrow.tsx new file mode 100644 index 0000000000..7aa73e66a0 --- /dev/null +++ b/src/components/icons/CloseNavbarArrow.tsx @@ -0,0 +1,67 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +interface Props { + color?: string; + size?: string; +} + +export function CloseNavbarArrow({ + color = '#A1A1AA', + size = '1.3rem', +}: Props) { + return ( + + + + + + ); +} diff --git a/src/components/icons/Exit.tsx b/src/components/icons/Exit.tsx new file mode 100644 index 0000000000..1a40a47244 --- /dev/null +++ b/src/components/icons/Exit.tsx @@ -0,0 +1,35 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +export function Exit() { + return ( + + + + + ); +} diff --git a/src/components/icons/ExpandCollapseChevron.tsx b/src/components/icons/ExpandCollapseChevron.tsx new file mode 100644 index 0000000000..0a34b01814 --- /dev/null +++ b/src/components/icons/ExpandCollapseChevron.tsx @@ -0,0 +1,45 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +interface Props { + color?: string; +} + +export function ExpandCollapseChevron({ color = '#FFFFFF' }: Props) { + return ( + + + + + ); +} diff --git a/src/components/icons/OpenNavbarArrow.tsx b/src/components/icons/OpenNavbarArrow.tsx new file mode 100644 index 0000000000..e8b65dd447 --- /dev/null +++ b/src/components/icons/OpenNavbarArrow.tsx @@ -0,0 +1,63 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +interface Props { + color?: string; + size?: string; +} + +export function OpenNavbarArrow({ color = '#74747C', size = '1.3rem' }: Props) { + return ( + + + + + + ); +} diff --git a/src/components/icons/Person.tsx b/src/components/icons/Person.tsx new file mode 100644 index 0000000000..1067eb617f --- /dev/null +++ b/src/components/icons/Person.tsx @@ -0,0 +1,43 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +export function Person() { + return ( + + + + + ); +} diff --git a/src/components/icons/Plus.tsx b/src/components/icons/Plus.tsx new file mode 100644 index 0000000000..9dcc3ec89c --- /dev/null +++ b/src/components/icons/Plus.tsx @@ -0,0 +1,47 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +export function Plus() { + return ( + + + + + ); +} diff --git a/src/components/layouts/Default.tsx b/src/components/layouts/Default.tsx index 7be736e6e7..837f03a17a 100644 --- a/src/components/layouts/Default.tsx +++ b/src/components/layouts/Default.tsx @@ -62,6 +62,7 @@ import { useSocketEvent } from '$app/common/queries/sockets'; import { Invoice } from '$app/common/interfaces/invoice'; import toast from 'react-hot-toast'; import { EInvoiceCredits } from '../banners/EInvoiceCredits'; +import classNames from 'classnames'; export interface SaveOption { label: string; @@ -423,12 +424,13 @@ export function Default(props: Props) {
+
- {isMiniSidebar ? ( - Company logo - ) : ( - - )} +
-
+