Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a tabbed navigation interface for the domain reseller feature, refactoring the dashboard into separate tabs for information and domain list views.
Changes:
- Restructured the domain reseller dashboard to use a tab-based layout with two tabs: general information and domains list
- Extracted dashboard content into a separate
DomainResellerInformationscomponent - Added routing configuration for tab navigation with automatic redirect to the information tab
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| routes.tsx | Updated routing structure to support nested tab routes with default redirect |
| routes.constants.ts | Added URL constants for the information and list tab routes |
| DomainsList.tsx | Created placeholder component for the domains list tab |
| DomainResellerInformations.tsx | Extracted dashboard content into dedicated informations tab component |
| DomainResellerDashboard.tsx | Refactored to render tabs and outlet instead of direct content |
| DomainResellerDashboard.spec.tsx | Removed obsolete tests that covered moved functionality |
| constants.ts | Defined tab configuration with IDs, names, and values |
| DomainResellerTabs.tsx | Implemented tab component with navigation logic |
| DomainResellerTabs.spec.tsx | Added tests for the tabs component |
| utils.ts | Added getDataFromEnvironment utility function |
| Messages_fr_FR.json | Added French translation for the domains tab label |
Comments suppressed due to low confidence (1)
packages/manager/apps/web-domains/src/common/utils/utils.ts:1
- React hooks like
useContextcannot be called directly from utility functions. This function will fail when called outside a React component context. Convert this to a custom hook by renaming it touseDataFromEnvironmentor refactor the calling code to pass the context data as parameters.
import { TServiceInfo } from '@/common/types/common.types';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| }); | ||
|
|
||
| describe('DomainResellerDashboard', () => { | ||
| beforeEach(() => { |
There was a problem hiding this comment.
The beforeEach hook is defined but contains no implementation. This test file has been significantly reduced and no longer provides adequate coverage for the dashboard component's new tab integration behavior.
ref: #DCE-167 Signed-off-by: Louis BENSI <louis.bensi@corp.ovh.com>
c8ac77a to
832fc5c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 11 changed files in this pull request and generated 8 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -1,55 +1,6 @@ | |||
| import { render, screen } from '@testing-library/react'; | |||
| import { describe, it, expect, vi, beforeEach } from 'vitest'; | |||
| import { describe, it, expect, vi } from 'vitest'; | |||
There was a problem hiding this comment.
beforeEach is used but no longer imported from vitest, which will cause the test file to crash at runtime. Add beforeEach back to the vitest import or remove the beforeEach usage.
| import { describe, it, expect, vi } from 'vitest'; | |
| import { beforeEach, describe, it, expect, vi } from 'vitest'; |
| /> | ||
| </div> | ||
| </section> | ||
| <BaseLayout header={header} tabs={DomainResellerTabs()}> |
There was a problem hiding this comment.
Passing DomainResellerTabs() calls a React component as a plain function even though it uses hooks (useState, useEffect, useNavigate, etc.). This breaks the Rules of Hooks and can lead to state being attached to the wrong component. Pass it as a React element instead (e.g., tabs={<DomainResellerTabs />}) or as a component reference if BaseLayout expects that.
| <BaseLayout header={header} tabs={DomainResellerTabs()}> | |
| <BaseLayout header={header} tabs={<DomainResellerTabs />}> |
| export const getDataFromEnvironment = () => { | ||
| const context = useContext(ShellContext); | ||
| const { ovhSubsidiary } = context.environment.getUser(); | ||
| const region = context.environment.getRegion(); | ||
|
|
||
| return { region, ovhSubsidiary }; | ||
| }; |
There was a problem hiding this comment.
getDataFromEnvironment calls useContext, so it should be implemented as a custom hook (and named accordingly, e.g. useEnvironmentData) to comply with hook conventions and avoid accidental usage outside React render. Consider moving it to a hooks module to prevent non-React callers from importing it as a generic utility.
| export const getDataFromEnvironment = () => { | |
| const context = useContext(ShellContext); | |
| const { ovhSubsidiary } = context.environment.getUser(); | |
| const region = context.environment.getRegion(); | |
| return { region, ovhSubsidiary }; | |
| }; | |
| export const useEnvironmentData = () => { | |
| const context = useContext(ShellContext); | |
| const { ovhSubsidiary } = context.environment.getUser(); | |
| const region = context.environment.getRegion(); | |
| return { region, ovhSubsidiary }; | |
| }; | |
| export const getDataFromEnvironment = () => { | |
| return useEnvironmentData(); | |
| }; |
| useEffect(() => { | ||
| const currentTab = DomainResellerTabsProps.find((tab) => | ||
| location.pathname.includes(tab.value), | ||
| ); | ||
| if (currentTab) { | ||
| setValue(currentTab.value); | ||
| } | ||
| }, [location.pathname]); | ||
|
|
||
| const handleValueChange = (event: TabsValueChangeEvent) => { | ||
| const selectedTab = DomainResellerTabsProps.find( | ||
| (tab) => tab.value === event.value, | ||
| ); | ||
| if (selectedTab) { | ||
| navigate(selectedTab.value, { replace: true }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Tab selection is inferred via includes(tab.value) and navigation uses a relative navigate(selectedTab.value), while route constants are absolute (e.g. /domain-reseller/information). This makes routing brittle (substring collisions and ambiguity about relative resolution). Prefer matching via router helpers (e.g. matchPath) and navigate using the canonical URL constants (or explicit relative paths tied to the parent route).
| import { DashboardTabItemProps } from '@/domain/types/serviceDetail'; | ||
| import { NAMESPACES } from '@ovh-ux/manager-common-translations'; | ||
|
|
||
| export const DomainResellerTabsProps: DashboardTabItemProps[] = [ |
There was a problem hiding this comment.
DomainResellerTabsProps is a configuration array, not React props. Renaming it to something like domainResellerTabs, domainResellerTabItems, or domainResellerTabsConfig would make its purpose clearer.
| export const DomainResellerTabsProps: DashboardTabItemProps[] = [ | |
| export const domainResellerTabsConfig: DashboardTabItemProps[] = [ |
| export default function DomainResellerDomainsList() { | ||
| return 'List des domaines'; |
There was a problem hiding this comment.
This tab returns a hard-coded French string. Prefer returning JSX and sourcing the label from i18n translations (consistent with the rest of the feature), e.g. via useTranslation('domain-reseller') and a translation key.
| export default function DomainResellerDomainsList() { | |
| return 'List des domaines'; | |
| import { useTranslation } from 'react-i18next'; | |
| export default function DomainResellerDomainsList() { | |
| const { t } = useTranslation('domain-reseller'); | |
| return <>{t('domainsList.tabLabel')}</>; |
| import { useGetDomainsList } from '@/domain-reseller/hooks/data/query'; | ||
| import Loading from '@/domain/components/Loading/Loading'; | ||
| import { getOrderURL } from '@ovh-ux/manager-module-order'; |
There was a problem hiding this comment.
After the refactor to a layout + <Outlet />, these imports appear unused in this file. If they’re no longer needed, remove them to keep the module focused and avoid lint/test failures in stricter configs.
| render(<DomainResellerDashboard />); | ||
| expect(screen.getByText('domain_reseller_title')).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
The dashboard is now primarily a layout wrapper that wires tabs + <Outlet />, but the test suite only checks the title. Add coverage asserting that the tabs are rendered/passed into BaseLayout, and that routed child content renders via the outlet (at least one happy-path route).
| export const getDataFromEnvironment = () => { | ||
| const context = useContext(ShellContext); | ||
| const { ovhSubsidiary } = context.environment.getUser(); | ||
| const region = context.environment.getRegion(); | ||
|
|
||
| return { region, ovhSubsidiary }; | ||
| }; |
| const navigate = useNavigate(); | ||
| const [value, setValue] = useState('information'); | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
please see ongoing operation implementation
ref: #DCE-167