From bd64cac29db0a49617e9518ea3ab633866af93e2 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:12:07 +0100 Subject: [PATCH 1/7] feat: add Preferences Dialog basic components and navigation --- .../preferences/components/Section.tsx | 42 ++++++++++++++ .../preferences/components/SectionItem.tsx | 55 +++++++++++++++++++ .../preferences/components/SectionList.tsx | 30 ++++++++++ .../components/SectionListWrapper.tsx | 21 +++++++ src/components/preferences/index.tsx | 32 +++++++++++ src/context/dialog-manager/types/index.ts | 1 - .../preferences/usePreferencesNavigation.tsx | 51 +++++++++++++++++ src/i18n/locales/en.json | 22 ++++++++ src/types/preferences/index.ts | 20 +++++++ 9 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/components/preferences/components/Section.tsx create mode 100644 src/components/preferences/components/SectionItem.tsx create mode 100644 src/components/preferences/components/SectionList.tsx create mode 100644 src/components/preferences/components/SectionListWrapper.tsx create mode 100644 src/components/preferences/index.tsx create mode 100644 src/hooks/preferences/usePreferencesNavigation.tsx create mode 100644 src/types/preferences/index.ts diff --git a/src/components/preferences/components/Section.tsx b/src/components/preferences/components/Section.tsx new file mode 100644 index 0000000..96fec1c --- /dev/null +++ b/src/components/preferences/components/Section.tsx @@ -0,0 +1,42 @@ +import { type ReactNode } from 'react'; +import { CaretLeftIcon, XIcon } from '@phosphor-icons/react'; + +const Section = ({ + className = '', + children, + title, + onBackButtonClicked, + onClosePreferences, +}: { + className?: string; + children: ReactNode; + title: string; + onBackButtonClicked?: () => void; + onClosePreferences: () => void; +}): JSX.Element => { + return ( +
+
+
+ {onBackButtonClicked && ( + + )} +

{title}

+
+ +
+
{children}
+
+ ); +}; + +export default Section; diff --git a/src/components/preferences/components/SectionItem.tsx b/src/components/preferences/components/SectionItem.tsx new file mode 100644 index 0000000..7836d33 --- /dev/null +++ b/src/components/preferences/components/SectionItem.tsx @@ -0,0 +1,55 @@ +export interface SectionItemProps { + text: string; + isActive?: boolean; + isSection?: boolean; + isSubsection?: boolean; + isDisabled?: boolean; + onClick?: () => void; + notificationsNumber?: number; +} + +const SectionItem = ({ + text, + isActive, + isDisabled, + isSection, + isSubsection, + notificationsNumber, + onClick, +}: SectionItemProps) => { + const isClickable = !!onClick; + const clickableContainerClass = isClickable ? 'hover:bg-gray-1 hover:bg-gray-5' : ''; + const activeContainerClass = isActive ? 'bg-primary' : clickableContainerClass; + const containerClass = isDisabled ? '' : activeContainerClass; + const clickableClass = isClickable ? 'hover:cursor-pointer' : ''; + const activeTextClass = isActive ? 'text-white' : 'text-gray-80'; + const disabledTextClass = isDisabled ? 'text-gray-40' : activeTextClass; + const sectionTextClass = isSection ? 'font-semibold' : ''; + const subsectionTextClass = isSubsection ? 'px-3' : ''; + const notificationClass = isActive ? 'bg-white' : ' bg-primary'; + const notificationTextClass = isActive ? 'text-primary' : 'text-white'; + + const Element = isClickable ? 'button' : 'div'; + + return ( + +
+ + {text} + +
+ {notificationsNumber && notificationsNumber > 0 && ( +
+ {notificationsNumber} +
+ )} +
+ ); +}; + +export default SectionItem; diff --git a/src/components/preferences/components/SectionList.tsx b/src/components/preferences/components/SectionList.tsx new file mode 100644 index 0000000..c62cfa2 --- /dev/null +++ b/src/components/preferences/components/SectionList.tsx @@ -0,0 +1,30 @@ +import type { PreferencesActivePath, PreferencesSectionItem } from '@/types/preferences'; +import SectionItem from './SectionItem'; + +interface SectionListProps { + sectionItems: (PreferencesSectionItem & { text: string })[]; + activePath: PreferencesActivePath; + onSelectSection: (path: PreferencesActivePath) => void; +} + +const SectionList = ({ sectionItems, activePath, onSelectSection }: SectionListProps) => { + return ( +
+ {sectionItems.map((item) => { + const isActive = item.section === activePath.section && item.subsection === activePath.subsection; + + return ( + onSelectSection({ section: item.section, subsection: item.subsection })} + /> + ); + })} +
+ ); +}; + +export default SectionList; diff --git a/src/components/preferences/components/SectionListWrapper.tsx b/src/components/preferences/components/SectionListWrapper.tsx new file mode 100644 index 0000000..1517e98 --- /dev/null +++ b/src/components/preferences/components/SectionListWrapper.tsx @@ -0,0 +1,21 @@ +import { useTranslationContext } from '@/i18n'; +import { PREFERENCES_DEFAULT_SECTIONS, type PreferencesActivePath } from '@/types/preferences'; +import SectionList from './SectionList'; + +interface SectionListWrapperProps { + activePath: PreferencesActivePath; + onSelectSection: (path: PreferencesActivePath) => void; +} + +const SectionListWrapper = ({ activePath, onSelectSection }: SectionListWrapperProps) => { + const { translate } = useTranslationContext(); + + const sectionItems = PREFERENCES_DEFAULT_SECTIONS.map((item) => ({ + ...item, + text: translate(`modals.preferences.sections.${item.subsection ?? item.section}.title`), + })); + + return ; +}; + +export default SectionListWrapper; diff --git a/src/components/preferences/index.tsx b/src/components/preferences/index.tsx new file mode 100644 index 0000000..5371fa5 --- /dev/null +++ b/src/components/preferences/index.tsx @@ -0,0 +1,32 @@ +import { usePreferencesNavigation } from '@/hooks/preferences/usePreferencesNavigation'; +import { useTranslationContext } from '@/i18n'; +import { Modal } from '@internxt/ui'; +import { useEffect } from 'react'; +import SectionListWrapper from './components/SectionListWrapper'; + +export const PreferencesDialog = () => { + const { translate } = useTranslationContext(); + const { isOpen, activePath, openSection, close } = usePreferencesNavigation(); + + const title = translate(`modals.preferences.sections.${activePath.subsection ?? activePath.section}.title`); + + useEffect(() => { + if (isOpen) { + document.title = `${title} | Internxt Mail`; + } + }, [isOpen, title]); + + return ( + +
+

{translate('modals.preferences.title')}

+ +
+
+ ); +}; diff --git a/src/context/dialog-manager/types/index.ts b/src/context/dialog-manager/types/index.ts index e65ea9d..4e65f8a 100644 --- a/src/context/dialog-manager/types/index.ts +++ b/src/context/dialog-manager/types/index.ts @@ -1,6 +1,5 @@ export enum ActionDialog { ComposeMessage = 'compose-message', - Settings = 'settings', } export interface ActionDialogState { diff --git a/src/hooks/preferences/usePreferencesNavigation.tsx b/src/hooks/preferences/usePreferencesNavigation.tsx new file mode 100644 index 0000000..baef92e --- /dev/null +++ b/src/hooks/preferences/usePreferencesNavigation.tsx @@ -0,0 +1,51 @@ +import { useSearchParams } from 'react-router-dom'; +import { useCallback, useMemo } from 'react'; +import { + PREFERENCES_DEFAULT_SECTIONS, + type PreferencesActivePath, + type PreferencesSection, + type PreferencesSubsection, +} from '@/types/preferences'; + +const DEFAULT_SECTION: PreferencesActivePath = { + section: 'general', + subsection: 'general', +}; + +const isValidSection = (value: string): value is PreferencesSection => { + return PREFERENCES_DEFAULT_SECTIONS.some((item) => item.section === value); +}; + +const isValidSubsection = (section: string, value: string): value is PreferencesSubsection => { + return PREFERENCES_DEFAULT_SECTIONS.some((item) => item.section === section && item.subsection === value); +}; + +export const usePreferencesNavigation = () => { + const [searchParams, setSearchParams] = useSearchParams(); + + const isOpen = searchParams.get('preferences') === 'open'; + + const activePath: PreferencesActivePath = useMemo(() => { + const section = searchParams.get('section'); + const subsection = searchParams.get('subsection'); + + if (section && isValidSection(section) && subsection && isValidSubsection(section, subsection)) { + return { section, subsection }; + } + + return DEFAULT_SECTION; + }, [searchParams]); + + const openSection = useCallback( + ({ section, subsection }: PreferencesActivePath) => { + setSearchParams({ preferences: 'open', section, subsection }, { replace: true }); + }, + [setSearchParams], + ); + + const close = useCallback(() => { + setSearchParams({}, { replace: true }); + }, [setSearchParams]); + + return { isOpen, activePath, openSection, close }; +}; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 66fd3b9..69ed961 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -113,6 +113,28 @@ "title": "Unlock Cleaner", "description": "Upgrade now to keep your files optimized and free up space." } + }, + "preferences": { + "title": "Preferences", + "sections": { + "general": { + "title": "General", + "appearance": { + "title": "Appearance", + "dark": "Dark", + "light": "Light", + "system": "System" + }, + "language": { + "title": "Language", + "en": "English (English)", + "es": "Español (Spanish)", + "fr": "Français (French)", + "it": "Italiano (Italian)" + }, + "support": "Support" + } + } } } } diff --git a/src/types/preferences/index.ts b/src/types/preferences/index.ts new file mode 100644 index 0000000..aba7d2e --- /dev/null +++ b/src/types/preferences/index.ts @@ -0,0 +1,20 @@ +export type PreferencesSection = 'general'; +export type PreferencesSubsection = 'general'; + +export interface PreferencesSectionItem { + section: PreferencesSection; + subsection: PreferencesSubsection; + isSubsection?: boolean; +} + +export interface PreferencesActivePath { + section: PreferencesSection; + subsection: PreferencesSubsection; +} + +export const PREFERENCES_DEFAULT_SECTIONS: PreferencesSectionItem[] = [ + { + section: 'general', + subsection: 'general', + }, +]; From 4c7d721072975e97def56ae8a25c0ba912b9240f Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 16:55:59 +0100 Subject: [PATCH 2/7] refactor: use only one param to identify the active tab --- .../preferences/components/Section.tsx | 6 ++-- .../preferences/components/SectionList.tsx | 29 +++++++--------- .../components/SectionListWrapper.tsx | 14 ++++---- src/components/preferences/index.tsx | 17 ++++++++-- .../preferences/sections/general/index.tsx | 14 ++++++++ .../preferences/usePreferencesNavigation.tsx | 33 ++++++------------- src/types/preferences/index.ts | 18 ++-------- 7 files changed, 63 insertions(+), 68 deletions(-) create mode 100644 src/components/preferences/sections/general/index.tsx diff --git a/src/components/preferences/components/Section.tsx b/src/components/preferences/components/Section.tsx index 96fec1c..c23896b 100644 --- a/src/components/preferences/components/Section.tsx +++ b/src/components/preferences/components/Section.tsx @@ -6,13 +6,13 @@ const Section = ({ children, title, onBackButtonClicked, - onClosePreferences, + onClose, }: { className?: string; children: ReactNode; title: string; onBackButtonClicked?: () => void; - onClosePreferences: () => void; + onClose: () => void; }): JSX.Element => { return (
@@ -29,7 +29,7 @@ const Section = ({
diff --git a/src/components/preferences/components/SectionList.tsx b/src/components/preferences/components/SectionList.tsx index c62cfa2..7eaf9e1 100644 --- a/src/components/preferences/components/SectionList.tsx +++ b/src/components/preferences/components/SectionList.tsx @@ -1,28 +1,23 @@ -import type { PreferencesActivePath, PreferencesSectionItem } from '@/types/preferences'; +import type { PreferencesSection, PreferencesSectionItem } from '@/types/preferences'; import SectionItem from './SectionItem'; interface SectionListProps { sectionItems: (PreferencesSectionItem & { text: string })[]; - activePath: PreferencesActivePath; - onSelectSection: (path: PreferencesActivePath) => void; + activeSection: PreferencesSection; + onSelectSection: (section: PreferencesSection) => void; } -const SectionList = ({ sectionItems, activePath, onSelectSection }: SectionListProps) => { +const SectionList = ({ sectionItems, activeSection, onSelectSection }: SectionListProps) => { return (
- {sectionItems.map((item) => { - const isActive = item.section === activePath.section && item.subsection === activePath.subsection; - - return ( - onSelectSection({ section: item.section, subsection: item.subsection })} - /> - ); - })} + {sectionItems.map((item) => ( + onSelectSection(item.id)} + /> + ))}
); }; diff --git a/src/components/preferences/components/SectionListWrapper.tsx b/src/components/preferences/components/SectionListWrapper.tsx index 1517e98..361ada6 100644 --- a/src/components/preferences/components/SectionListWrapper.tsx +++ b/src/components/preferences/components/SectionListWrapper.tsx @@ -1,21 +1,21 @@ import { useTranslationContext } from '@/i18n'; -import { PREFERENCES_DEFAULT_SECTIONS, type PreferencesActivePath } from '@/types/preferences'; +import { PREFERENCES_SECTIONS, type PreferencesSection } from '@/types/preferences'; import SectionList from './SectionList'; interface SectionListWrapperProps { - activePath: PreferencesActivePath; - onSelectSection: (path: PreferencesActivePath) => void; + activeSection: PreferencesSection; + onSelectSection: (section: PreferencesSection) => void; } -const SectionListWrapper = ({ activePath, onSelectSection }: SectionListWrapperProps) => { +const SectionListWrapper = ({ activeSection, onSelectSection }: SectionListWrapperProps) => { const { translate } = useTranslationContext(); - const sectionItems = PREFERENCES_DEFAULT_SECTIONS.map((item) => ({ + const sectionItems = PREFERENCES_SECTIONS.map((item) => ({ ...item, - text: translate(`modals.preferences.sections.${item.subsection ?? item.section}.title`), + text: translate(`modals.preferences.sections.${item.id}.title`), })); - return ; + return ; }; export default SectionListWrapper; diff --git a/src/components/preferences/index.tsx b/src/components/preferences/index.tsx index 5371fa5..5af2f6e 100644 --- a/src/components/preferences/index.tsx +++ b/src/components/preferences/index.tsx @@ -1,14 +1,21 @@ import { usePreferencesNavigation } from '@/hooks/preferences/usePreferencesNavigation'; import { useTranslationContext } from '@/i18n'; +import type { PreferencesSection } from '@/types/preferences'; import { Modal } from '@internxt/ui'; +import type { FC } from 'react'; import { useEffect } from 'react'; import SectionListWrapper from './components/SectionListWrapper'; +import GeneralSection from './sections/general'; + +const SECTION_COMPONENTS: Record void }>> = { + general: GeneralSection, +}; export const PreferencesDialog = () => { const { translate } = useTranslationContext(); - const { isOpen, activePath, openSection, close } = usePreferencesNavigation(); + const { isOpen, activeSection, openSection, close } = usePreferencesNavigation(); - const title = translate(`modals.preferences.sections.${activePath.subsection ?? activePath.section}.title`); + const title = translate(`modals.preferences.sections.${activeSection}.title`); useEffect(() => { if (isOpen) { @@ -16,6 +23,8 @@ export const PreferencesDialog = () => { } }, [isOpen, title]); + const ActiveSectionComponent = SECTION_COMPONENTS[activeSection]; + return ( { >

{translate('modals.preferences.title')}

- +
+ +
); }; diff --git a/src/components/preferences/sections/general/index.tsx b/src/components/preferences/sections/general/index.tsx new file mode 100644 index 0000000..765eb25 --- /dev/null +++ b/src/components/preferences/sections/general/index.tsx @@ -0,0 +1,14 @@ +import { useTranslationContext } from '@/i18n'; +import Section from '../../components/Section'; + +const GeneralSection = ({ onClose }: { onClose: () => void }) => { + const { translate } = useTranslationContext(); + return ( +
+ {/* TODO: Add appearance, language and support components */} +

{translate('modals.preferences.sections.general.title')}

+
+ ); +}; + +export default GeneralSection; diff --git a/src/hooks/preferences/usePreferencesNavigation.tsx b/src/hooks/preferences/usePreferencesNavigation.tsx index baef92e..189f51d 100644 --- a/src/hooks/preferences/usePreferencesNavigation.tsx +++ b/src/hooks/preferences/usePreferencesNavigation.tsx @@ -1,23 +1,11 @@ import { useSearchParams } from 'react-router-dom'; import { useCallback, useMemo } from 'react'; -import { - PREFERENCES_DEFAULT_SECTIONS, - type PreferencesActivePath, - type PreferencesSection, - type PreferencesSubsection, -} from '@/types/preferences'; - -const DEFAULT_SECTION: PreferencesActivePath = { - section: 'general', - subsection: 'general', -}; +import { PREFERENCES_SECTIONS, type PreferencesSection } from '@/types/preferences'; -const isValidSection = (value: string): value is PreferencesSection => { - return PREFERENCES_DEFAULT_SECTIONS.some((item) => item.section === value); -}; +const DEFAULT_SECTION: PreferencesSection = 'general'; -const isValidSubsection = (section: string, value: string): value is PreferencesSubsection => { - return PREFERENCES_DEFAULT_SECTIONS.some((item) => item.section === section && item.subsection === value); +const isValidSection = (value: string): value is PreferencesSection => { + return PREFERENCES_SECTIONS.some((item) => item.id === value); }; export const usePreferencesNavigation = () => { @@ -25,20 +13,19 @@ export const usePreferencesNavigation = () => { const isOpen = searchParams.get('preferences') === 'open'; - const activePath: PreferencesActivePath = useMemo(() => { + const activeSection: PreferencesSection = useMemo(() => { const section = searchParams.get('section'); - const subsection = searchParams.get('subsection'); - if (section && isValidSection(section) && subsection && isValidSubsection(section, subsection)) { - return { section, subsection }; + if (section && isValidSection(section)) { + return section; } return DEFAULT_SECTION; }, [searchParams]); const openSection = useCallback( - ({ section, subsection }: PreferencesActivePath) => { - setSearchParams({ preferences: 'open', section, subsection }, { replace: true }); + (section: PreferencesSection) => { + setSearchParams({ preferences: 'open', section }, { replace: true }); }, [setSearchParams], ); @@ -47,5 +34,5 @@ export const usePreferencesNavigation = () => { setSearchParams({}, { replace: true }); }, [setSearchParams]); - return { isOpen, activePath, openSection, close }; + return { isOpen, activeSection, openSection, close }; }; diff --git a/src/types/preferences/index.ts b/src/types/preferences/index.ts index aba7d2e..5361065 100644 --- a/src/types/preferences/index.ts +++ b/src/types/preferences/index.ts @@ -1,20 +1,8 @@ export type PreferencesSection = 'general'; -export type PreferencesSubsection = 'general'; export interface PreferencesSectionItem { - section: PreferencesSection; - subsection: PreferencesSubsection; - isSubsection?: boolean; + id: PreferencesSection; + group?: string; } -export interface PreferencesActivePath { - section: PreferencesSection; - subsection: PreferencesSubsection; -} - -export const PREFERENCES_DEFAULT_SECTIONS: PreferencesSectionItem[] = [ - { - section: 'general', - subsection: 'general', - }, -]; +export const PREFERENCES_SECTIONS: PreferencesSectionItem[] = [{ id: 'general' }]; From 6e56c87fbfaac736c02c3ab31d3573974871854a Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 17:38:49 +0100 Subject: [PATCH 3/7] feat: add settings component to open Preferences Dialog --- src/features/mail/MailView.tsx | 8 +++- .../mail/components/settings/index.tsx | 45 +++++++++++++++++++ src/routes/layouts/SidebarAndHeaderLayout.tsx | 3 ++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/features/mail/components/settings/index.tsx diff --git a/src/features/mail/MailView.tsx b/src/features/mail/MailView.tsx index 3603606..8c6590b 100644 --- a/src/features/mail/MailView.tsx +++ b/src/features/mail/MailView.tsx @@ -4,6 +4,7 @@ import { getMockedMail } from '@/test-utils/fixtures'; import PreviewMail from './components/mail-preview'; import type { User } from './components/mail-preview/header'; import TrayList from './components/tray'; +import Settings from './components/settings'; interface MailViewProps { folder: FolderType; @@ -24,7 +25,12 @@ const MailView = ({ folder }: MailViewProps) => { {/* Tray */} {/* Mail Preview */} - +
+
+ +
+ +
); }; diff --git a/src/features/mail/components/settings/index.tsx b/src/features/mail/components/settings/index.tsx new file mode 100644 index 0000000..4256b20 --- /dev/null +++ b/src/features/mail/components/settings/index.tsx @@ -0,0 +1,45 @@ +import { usePreferencesNavigation } from '@/hooks/preferences/usePreferencesNavigation'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import { logoutThunk } from '@/store/slices/user/thunks'; +import { Avatar, Button, Dropdown } from '@internxt/ui'; +import { GearIcon } from '@phosphor-icons/react'; + +const Settings = () => { + const dispatch = useAppDispatch(); + const user = useAppSelector((state) => state.user.user); + const { openSection } = usePreferencesNavigation(); + + if (!user) return null; + + const openPreferences = () => { + openSection('general'); + }; + + const onLogOut = async () => { + await dispatch(logoutThunk()); + }; + + const dropdownActions = [ + { + name: 'Settings', + onClick: openPreferences, + }, + { + name: 'Logout', + onClick: onLogOut, + }, + ]; + + return ( +
+ + + + +
+ ); +}; + +export default Settings; diff --git a/src/routes/layouts/SidebarAndHeaderLayout.tsx b/src/routes/layouts/SidebarAndHeaderLayout.tsx index 4a49ecb..22d5233 100644 --- a/src/routes/layouts/SidebarAndHeaderLayout.tsx +++ b/src/routes/layouts/SidebarAndHeaderLayout.tsx @@ -1,6 +1,7 @@ import { Outlet } from 'react-router-dom'; import { Suspense } from 'react'; import Sidenav from '@/components/Sidenav'; +import { PreferencesDialog } from '@/components/preferences'; /** * App layout (contains the static components like the sidebar) @@ -15,6 +16,8 @@ const SidebarAndHeaderLayout = () => { + + ); }; From 834009867246722118209d4a49000290c1979ee2 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:17:30 +0100 Subject: [PATCH 4/7] feat: account popover --- .../components/account-popover/index.tsx | 86 +++++++++++++++++++ .../mail/components/settings/index.tsx | 34 ++++---- src/i18n/locales/en.json | 5 ++ 3 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 src/features/mail/components/settings/components/account-popover/index.tsx diff --git a/src/features/mail/components/settings/components/account-popover/index.tsx b/src/features/mail/components/settings/components/account-popover/index.tsx new file mode 100644 index 0000000..2886f17 --- /dev/null +++ b/src/features/mail/components/settings/components/account-popover/index.tsx @@ -0,0 +1,86 @@ +import { useTranslationContext } from '@/i18n'; +import type { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; +import { Avatar, Popover } from '@internxt/ui'; +import { GearIcon, SignOutIcon } from '@phosphor-icons/react'; +import type { ReactNode } from 'react'; + +interface AccountPopoverProps { + className?: string; + user: UserSettings; + percentageUsed?: number; + onLogout: () => void; + openPreferences: () => void; +} + +export default function AccountPopover({ + className = '', + user, + percentageUsed, + onLogout, + openPreferences, +}: Readonly): JSX.Element { + const { translate } = useTranslationContext(); + const name = user?.name ?? ''; + const lastName = user?.lastname ?? ''; + const fullName = name + ' ' + lastName; + + const avatarWrapper = ; + + const separator =
; + + const panel = ( +
+
+ {avatarWrapper} +
+

+ {fullName} +

+

+ {user.email} +

+
+
+ +
+

{translate('accountPopover.spaceUsed', { space: percentageUsed })}

+
+ {separator} + + + +

+ {translate('accountPopover.logout')} +

+
+
+ ); + + return ( + panel} data-test="app-header-dropdown" /> + ); +} + +interface ItemProps { + children: ReactNode; + onClick: () => void; +} + +function Item({ children, onClick }: Readonly) { + return ( +
+ {children} +
+ ); +} diff --git a/src/features/mail/components/settings/index.tsx b/src/features/mail/components/settings/index.tsx index 4256b20..2078542 100644 --- a/src/features/mail/components/settings/index.tsx +++ b/src/features/mail/components/settings/index.tsx @@ -1,43 +1,41 @@ import { usePreferencesNavigation } from '@/hooks/preferences/usePreferencesNavigation'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; -import { logoutThunk } from '@/store/slices/user/thunks'; -import { Avatar, Button, Dropdown } from '@internxt/ui'; +import { useGetStorageLimitQuery, useGetStorageUsageQuery } from '@/store/queries/storage/storage.query'; +import { Button } from '@internxt/ui'; import { GearIcon } from '@phosphor-icons/react'; +import AccountPopover from './components/account-popover'; +import { logoutThunk } from '@/store/slices/user/thunks'; const Settings = () => { const dispatch = useAppDispatch(); const user = useAppSelector((state) => state.user.user); + const { data: usage } = useGetStorageUsageQuery(); + const { data: limit } = useGetStorageLimitQuery(); const { openSection } = usePreferencesNavigation(); + const percentageUsed = usage != null && limit ? Math.round((usage / limit) * 100) : 0; + if (!user) return null; const openPreferences = () => { openSection('general'); }; - const onLogOut = async () => { - await dispatch(logoutThunk()); + const onLogout = () => { + dispatch(logoutThunk()); }; - const dropdownActions = [ - { - name: 'Settings', - onClick: openPreferences, - }, - { - name: 'Logout', - onClick: onLogOut, - }, - ]; - return (
- - - +
); }; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 69ed961..d30ecb2 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -136,5 +136,10 @@ } } } + }, + "accountPopover": { + "spaceUsed": "{{space}}% space used", + "settings": "Settings", + "logout": "Log out" } } From df459d16054f597f556d8c119ed3da64daec787e Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:42:16 +0100 Subject: [PATCH 5/7] fix: make percentage used mandatory --- src/components/preferences/components/SectionItem.tsx | 8 ++++---- src/components/preferences/index.tsx | 9 ++++++--- .../settings/components/account-popover/index.tsx | 7 +++---- src/features/mail/components/settings/index.tsx | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/components/preferences/components/SectionItem.tsx b/src/components/preferences/components/SectionItem.tsx index 7836d33..0ff960d 100644 --- a/src/components/preferences/components/SectionItem.tsx +++ b/src/components/preferences/components/SectionItem.tsx @@ -17,8 +17,8 @@ const SectionItem = ({ notificationsNumber, onClick, }: SectionItemProps) => { - const isClickable = !!onClick; - const clickableContainerClass = isClickable ? 'hover:bg-gray-1 hover:bg-gray-5' : ''; + const isClickable = !!onClick && !isDisabled; + const clickableContainerClass = isClickable ? 'hover:bg-gray-5' : ''; const activeContainerClass = isActive ? 'bg-primary' : clickableContainerClass; const containerClass = isDisabled ? '' : activeContainerClass; const clickableClass = isClickable ? 'hover:cursor-pointer' : ''; @@ -35,7 +35,7 @@ const SectionItem = ({
@@ -44,7 +44,7 @@ const SectionItem = ({
{notificationsNumber && notificationsNumber > 0 && ( -
+
{notificationsNumber}
)} diff --git a/src/components/preferences/index.tsx b/src/components/preferences/index.tsx index 5af2f6e..4116bd0 100644 --- a/src/components/preferences/index.tsx +++ b/src/components/preferences/index.tsx @@ -18,9 +18,12 @@ export const PreferencesDialog = () => { const title = translate(`modals.preferences.sections.${activeSection}.title`); useEffect(() => { - if (isOpen) { - document.title = `${title} | Internxt Mail`; - } + if (!isOpen) return; + const previousTitle = document.title; + document.title = `${title} | Internxt Mail`; + return () => { + document.title = previousTitle; + }; }, [isOpen, title]); const ActiveSectionComponent = SECTION_COMPONENTS[activeSection]; diff --git a/src/features/mail/components/settings/components/account-popover/index.tsx b/src/features/mail/components/settings/components/account-popover/index.tsx index 2886f17..3a2befa 100644 --- a/src/features/mail/components/settings/components/account-popover/index.tsx +++ b/src/features/mail/components/settings/components/account-popover/index.tsx @@ -7,7 +7,7 @@ import type { ReactNode } from 'react'; interface AccountPopoverProps { className?: string; user: UserSettings; - percentageUsed?: number; + percentageUsed: number; onLogout: () => void; openPreferences: () => void; } @@ -74,13 +74,12 @@ interface ItemProps { function Item({ children, onClick }: Readonly) { return ( -
{children} -
+ ); } diff --git a/src/features/mail/components/settings/index.tsx b/src/features/mail/components/settings/index.tsx index 2078542..b568bab 100644 --- a/src/features/mail/components/settings/index.tsx +++ b/src/features/mail/components/settings/index.tsx @@ -13,7 +13,7 @@ const Settings = () => { const { data: limit } = useGetStorageLimitQuery(); const { openSection } = usePreferencesNavigation(); - const percentageUsed = usage != null && limit ? Math.round((usage / limit) * 100) : 0; + const percentageUsed = usage != null && limit != null && limit > 0 ? Math.round((usage / limit) * 100) : 0; if (!user) return null; From 33cdfcfe3500e55a10dfbe94e7fe521e6fa46b89 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+xabg2@users.noreply.github.com> Date: Tue, 24 Mar 2026 18:48:32 +0100 Subject: [PATCH 6/7] fix: remove useless JSX.Element cast --- src/components/preferences/components/Section.tsx | 12 ++++-------- .../settings/components/account-popover/index.tsx | 2 +- src/features/mail/components/settings/index.tsx | 2 +- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/preferences/components/Section.tsx b/src/components/preferences/components/Section.tsx index c23896b..0eed257 100644 --- a/src/components/preferences/components/Section.tsx +++ b/src/components/preferences/components/Section.tsx @@ -1,19 +1,15 @@ import { type ReactNode } from 'react'; import { CaretLeftIcon, XIcon } from '@phosphor-icons/react'; -const Section = ({ - className = '', - children, - title, - onBackButtonClicked, - onClose, -}: { +interface SectionProps { className?: string; children: ReactNode; title: string; onBackButtonClicked?: () => void; onClose: () => void; -}): JSX.Element => { +} + +const Section = ({ className = '', children, title, onBackButtonClicked, onClose }: SectionProps) => { return (
diff --git a/src/features/mail/components/settings/components/account-popover/index.tsx b/src/features/mail/components/settings/components/account-popover/index.tsx index 3a2befa..0d67b98 100644 --- a/src/features/mail/components/settings/components/account-popover/index.tsx +++ b/src/features/mail/components/settings/components/account-popover/index.tsx @@ -18,7 +18,7 @@ export default function AccountPopover({ percentageUsed, onLogout, openPreferences, -}: Readonly): JSX.Element { +}: Readonly) { const { translate } = useTranslationContext(); const name = user?.name ?? ''; const lastName = user?.lastname ?? ''; diff --git a/src/features/mail/components/settings/index.tsx b/src/features/mail/components/settings/index.tsx index b568bab..7f55683 100644 --- a/src/features/mail/components/settings/index.tsx +++ b/src/features/mail/components/settings/index.tsx @@ -27,7 +27,7 @@ const Settings = () => { return (
- Date: Tue, 24 Mar 2026 18:52:32 +0100 Subject: [PATCH 7/7] fix: remove useless props --- .../preferences/components/SectionItem.tsx | 24 ++----------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/components/preferences/components/SectionItem.tsx b/src/components/preferences/components/SectionItem.tsx index 0ff960d..be1cd5a 100644 --- a/src/components/preferences/components/SectionItem.tsx +++ b/src/components/preferences/components/SectionItem.tsx @@ -2,21 +2,11 @@ export interface SectionItemProps { text: string; isActive?: boolean; isSection?: boolean; - isSubsection?: boolean; isDisabled?: boolean; onClick?: () => void; - notificationsNumber?: number; } -const SectionItem = ({ - text, - isActive, - isDisabled, - isSection, - isSubsection, - notificationsNumber, - onClick, -}: SectionItemProps) => { +const SectionItem = ({ text, isActive, isDisabled, isSection, onClick }: SectionItemProps) => { const isClickable = !!onClick && !isDisabled; const clickableContainerClass = isClickable ? 'hover:bg-gray-5' : ''; const activeContainerClass = isActive ? 'bg-primary' : clickableContainerClass; @@ -25,9 +15,6 @@ const SectionItem = ({ const activeTextClass = isActive ? 'text-white' : 'text-gray-80'; const disabledTextClass = isDisabled ? 'text-gray-40' : activeTextClass; const sectionTextClass = isSection ? 'font-semibold' : ''; - const subsectionTextClass = isSubsection ? 'px-3' : ''; - const notificationClass = isActive ? 'bg-white' : ' bg-primary'; - const notificationTextClass = isActive ? 'text-primary' : 'text-white'; const Element = isClickable ? 'button' : 'div'; @@ -39,15 +26,8 @@ const SectionItem = ({ {...(isClickable ? { type: 'button' as const } : {})} >
- - {text} - + {text}
- {notificationsNumber && notificationsNumber > 0 && ( -
- {notificationsNumber} -
- )} ); };