diff --git a/packages/manager/apps/pci-savings-plan/package.json b/packages/manager/apps/pci-savings-plan/package.json
index 036b4dfa2e5b..d2b5a003bc56 100644
--- a/packages/manager/apps/pci-savings-plan/package.json
+++ b/packages/manager/apps/pci-savings-plan/package.json
@@ -23,16 +23,14 @@
"@ovh-ux/manager-config": "^8.0.2",
"@ovh-ux/manager-core-api": "^0.9.0",
"@ovh-ux/manager-core-utils": "*",
- "@ovh-ux/manager-pci-common": "^0.14.2",
- "@ovh-ux/manager-react-components": "^1.43.0",
+ "@ovh-ux/manager-pci-common": "^1.0.4",
"@ovh-ux/manager-react-core-application": "^0.11.5",
"@ovh-ux/manager-react-shell-client": "^0.8.5",
+ "@ovh-ux/manager-react-components": "^2.5.1",
"@ovh-ux/manager-tailwind-config": "^0.2.1",
"@ovh-ux/request-tagger": "^0.4.0",
- "@ovhcloud/ods-common-core": "17.2.2",
- "@ovhcloud/ods-common-theming": "17.2.2",
- "@ovhcloud/ods-components": "17.2.2",
- "@ovhcloud/ods-theme-blue-jeans": "17.2.1",
+ "@ovhcloud/ods-components": "^18.4.1",
+ "@ovhcloud/ods-themes": "^18.4.1",
"@tanstack/react-query": "5.51.11",
"@tanstack/react-query-devtools": "5.29.2",
"@testing-library/react": "^16.0.0",
diff --git a/packages/manager/apps/pci-savings-plan/src/App.tsx b/packages/manager/apps/pci-savings-plan/src/App.tsx
index 641830d51bad..931cb50149fa 100644
--- a/packages/manager/apps/pci-savings-plan/src/App.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/App.tsx
@@ -1,12 +1,9 @@
import React, { useEffect, useContext } from 'react';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
-import { odsSetup } from '@ovhcloud/ods-common-core';
import { ShellContext } from '@ovh-ux/manager-react-shell-client';
import { RouterProvider, createHashRouter } from 'react-router-dom';
import { Routes } from './routes/routes';
-odsSetup();
-
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/apps/pci-savings-plan/src/components/Breadcrumb/Breadcrumb.tsx
index da40a1a4751a..5e59eadb1dda 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Breadcrumb/Breadcrumb.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Breadcrumb/Breadcrumb.tsx
@@ -1,7 +1,10 @@
import React, { useState } from 'react';
import { Params, useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { OsdsBreadcrumb } from '@ovhcloud/ods-components/react';
+import {
+ OdsBreadcrumb,
+ OdsBreadcrumbItem,
+} from '@ovhcloud/ods-components/react';
import { useNavigation } from '@ovh-ux/manager-react-shell-client';
import { useProject } from '@ovh-ux/manager-pci-common';
@@ -16,6 +19,7 @@ const getPageName = (location: string, t: (key: string) => string) => {
return [
{
label: t('createSavingsPlan'),
+ href: location,
},
];
}
@@ -45,20 +49,28 @@ const Breadcrumb: React.FC = () => {
updateNav();
}, [navigation, projectId]);
+ const breadcrumbItems = [
+ {
+ href: urlProject,
+ label: project?.description,
+ },
+ {
+ href: `${urlProject}/savings-plan`,
+ label: t('title'),
+ },
+ ...items,
+ ];
+
return (
-
+
+ {breadcrumbItems.map((item) => (
+
+ ))}
+
);
};
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.test.tsx b/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.test.tsx
index 0c8c76629247..74ef0835fb6f 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.test.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.test.tsx
@@ -7,6 +7,12 @@ import '@testing-library/jest-dom';
import { render } from '@/utils/testProvider';
+vi.mock('@ovh-ux/manager-react-components', () => ({
+ useCatalogPrice: vi.fn().mockReturnValue({
+ getTextPrice: vi.fn().mockReturnValue('€10.00'),
+ }),
+}));
+
const defaultProps = {
duration: 1,
price: '1',
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.tsx b/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.tsx
index 83b75be5e39f..9cab2ccb4273 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Commitment/Commitment.tsx
@@ -1,17 +1,13 @@
-import {
- ODS_THEME_COLOR_INTENT,
- ODS_THEME_TYPOGRAPHY_SIZE,
-} from '@ovhcloud/ods-common-theming';
import {
ButtonType,
PageLocation,
useOvhTracking,
} from '@ovh-ux/manager-react-shell-client';
-import { ODS_TILE_SIZE, ODS_TILE_VARIANT } from '@ovhcloud/ods-components';
-import { OsdsText, OsdsTile } from '@ovhcloud/ods-components/react';
+import { OdsText, OdsCard } from '@ovhcloud/ods-components/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { useCatalogPrice } from '@ovh-ux/manager-react-components';
+import clsx from 'clsx';
import { getDiffInPercent } from './Commitment.utils';
import {
CENTS_PRICE,
@@ -57,54 +53,40 @@ const Commitment = ({
};
return (
-
-
-
- {t('commitment_month', { value: duration })}
-
-
- {diffInPercent ? `- ${diffInPercent} %` : ''}
-
+
+ {t('commitment_month', { value: duration })}
+ {diffInPercent && (
+
+
+ {`- ${diffInPercent} %`}
+
+
+ )}
-
+
{priceByMonthWithoutCommitment && (
-
+
{`~ ${getTextPrice(priceByMonthWithoutCommitment * CENTS_PRICE)}`}
-
+
)}
-
- {getTextPrice(priceNumber * CENTS_PRICE)}
-
+
+
+ {getTextPrice(priceNumber * CENTS_PRICE)}
+
+
- {t('commitment_price_month')}
+ {t('commitment_price_month')}
-
+
);
};
diff --git a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlan.test.tsx b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlan.test.tsx
index ce2af2758cb8..96e5a8f45fcd 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlan.test.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlan.test.tsx
@@ -11,6 +11,10 @@ import {
import { render } from '@/utils/testProvider';
+vi.mock('@ovh-ux/manager-pci-common', () => ({
+ usePciUrl: vi.fn(() => '/url'),
+}));
+
vi.mock('react-i18next', () => ({
useTranslation: vi.fn().mockReturnValue({
t: (key: string) => key,
@@ -38,10 +42,6 @@ vi.mock('react-router-dom', async (importOriginal) => {
};
});
-vi.mock('@ovh-ux/manager-pci-common', () => ({
- usePciUrl: vi.fn(() => '/url'),
-}));
-
const mockOnCreatePlan = vi.fn();
const mockSetTechnicalModel = vi.fn();
@@ -139,30 +139,21 @@ describe('CreatePlanForm', () => {
// Select duration
fireEvent.click(screen.getByText('commitment_month'));
- // Select quantity
- const plusButton = screen.getByTestId('plus-button');
- fireEvent.click(plusButton);
// Select model
fireEvent.click(screen.getByText('select_model_description_instance_b3'));
// Accept legal checkbox
fireEvent.click(screen.getByText('legal_checkbox'));
// Click on create button
- fireEvent.click(screen.getByText('cta_plan'));
+ fireEvent.click(screen.getByTestId('cta-plan-button'));
expect(defaultProps.onCreatePlan).toHaveBeenCalled();
});
- it('When no instance selected, create button should be disabled ', async () => {
- await setupSpecTest();
-
- fireEvent.click(screen.getByText('cta_plan'));
- expect(screen.getByText('cta_plan')).toHaveAttribute('disabled');
- });
-
it('should not call onCreatePlan if form not valid', async () => {
await setupSpecTest();
- fireEvent.click(screen.getByText('cta_plan'));
+ const ctaPlanButton = screen.getByTestId('cta-plan-button');
+ fireEvent.click(ctaPlanButton);
expect(defaultProps.onCreatePlan).not.toHaveBeenCalled();
});
diff --git a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx
index 25c72e190b88..6f8c6d7cbd33 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/CreatePlanForm/CreatePlanForm.tsx
@@ -1,52 +1,47 @@
import { usePciUrl } from '@ovh-ux/manager-pci-common';
-import { Description, Subtitle } from '@ovh-ux/manager-react-components';
-import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core';
-import {
- ODS_THEME_COLOR_INTENT,
- ODS_THEME_TYPOGRAPHY_LEVEL,
- ODS_THEME_TYPOGRAPHY_SIZE,
-} from '@ovhcloud/ods-common-theming';
+import { Subtitle, useNotifications } from '@ovh-ux/manager-react-components';
import {
ODS_BUTTON_VARIANT,
- ODS_CHECKBOX_BUTTON_SIZE,
ODS_ICON_NAME,
- ODS_ICON_SIZE,
ODS_INPUT_TYPE,
- ODS_MESSAGE_TYPE,
ODS_SPINNER_SIZE,
- ODS_TILE_VARIANT,
- OdsInputValueChangeEventDetail,
- OsdsInputCustomEvent,
+ OdsInputChangeEvent,
} from '@ovhcloud/ods-components';
+
import {
- OsdsButton,
- OsdsCheckbox,
- OsdsCheckboxButton,
- OsdsInput,
- OsdsLink,
- OsdsMessage,
- OsdsSpinner,
- OsdsTabBar,
- OsdsTabBarItem,
- OsdsTabs,
- OsdsText,
- OsdsTile,
- OsdsIcon,
+ OdsButton,
+ OdsCard,
+ OdsCheckbox,
+ OdsInput,
+ OdsLink,
+ OdsMessage,
+ OdsQuantity,
+ OdsSpinner,
+ OdsTab,
+ OdsTabs,
+ OdsText,
} from '@ovhcloud/ods-components/react';
-import React, { FC, useEffect, useMemo, useState, Suspense } from 'react';
+import React, {
+ FC,
+ Suspense,
+ useCallback,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+} from 'react';
+import { MutationStatus, useMutationState } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
-import { MutationStatus, useMutationState } from '@tanstack/react-query';
import {
ButtonType,
PageLocation,
useOvhTracking,
+ ShellContext,
} from '@ovh-ux/manager-react-shell-client';
-import QuantitySelector, {
- MAX_QUANTITY,
-} from '@/components/QuantitySelector/QuantitySelector';
+import clsx from 'clsx';
import useTechnicalInfo, { usePricingInfo } from '@/hooks/useCatalogCommercial';
import {
getMutationKeyCreateSavingsPlan,
@@ -55,31 +50,32 @@ import {
} from '@/hooks/useSavingsPlan';
import rancherSrc from '../../assets/images/rancher.png';
import serviceSrc from '../../assets/images/service.png';
+import {
+ PricingByDurationType,
+ useDefaultOfferId,
+} from '../../hooks/planCreation/useDefaultOffer';
import {
InstanceInfo,
InstanceTechnicalName,
Resource,
ResourceType,
} from '../../types/CreatePlan.type';
-import { formatDate } from '../../utils/formatter/date';
+import { formatDate, toLocalDateUTC } from '../../utils/formatter/date';
import { isValidSavingsPlanName } from '../../utils/savingsPlan';
import Commitment from '../Commitment/Commitment';
-import LegalLinks from '../LegalLinks/LegalLinks';
import SimpleTile from '../SimpleTile/SimpleTile';
import { TileTechnicalInfo } from '../TileTechnicalInfo/TileTechnicalInfo';
-import {
- PricingByDurationType,
- useDefaultOfferId,
-} from '../../hooks/planCreation/useDefaultOffer';
+import LegalLinks from '../LegalLinks/LegalLinks';
const COMMON_SPACING = 'my-4';
export const DescriptionWrapper: React.FC<{
children: string;
-}> = ({ children }) => {
+ className?: string;
+}> = ({ children, className }) => {
return (
-
-
{children}
+
+ {children}
);
};
@@ -195,6 +191,8 @@ const CreatePlanForm: FC
= ({
)
: [];
+ const isValidPlanName = isValidSavingsPlanName(planName);
+
const isButtonActive = useMemo(
() =>
quantity > 0 &&
@@ -203,7 +201,8 @@ const CreatePlanForm: FC = ({
selectedResource &&
isLegalChecked &&
planName &&
- !isDiscoveryProject,
+ !isDiscoveryProject &&
+ isValidPlanName,
[
quantity,
offerIdSelected,
@@ -212,6 +211,7 @@ const CreatePlanForm: FC = ({
isLegalChecked,
planName,
isDiscoveryProject,
+ isValidPlanName,
],
);
@@ -246,6 +246,12 @@ const CreatePlanForm: FC = ({
const onCreateSavingsPlan = () => {
if (offerIdSelected) {
+ trackClick({
+ location: PageLocation.funnel,
+ buttonType: ButtonType.button,
+ actionType: 'action',
+ actions: [`add_savings_plan::cancell`],
+ });
onCreatePlan({
offerId: offerIdSelected,
displayName: planName,
@@ -254,21 +260,28 @@ const CreatePlanForm: FC = ({
}
};
- const onChangeQuantity = (v: number) => setQuantity(v);
+ const handleQuantityChange = useCallback(
+ (event: OdsInputChangeEvent) => {
+ const newValue = Number(event.detail.value);
+ if (newValue >= 1 && newValue <= 1000) {
+ setQuantity(newValue);
+ } else {
+ setQuantity(1);
+ }
+ },
+ [setQuantity],
+ );
return (
{hasCreationErrorMessage && (
-
-
+
+
Une erreur est survenue lors de la création :
{hasCreationErrorMessage}
-
-
+
+
)}
{t('choose_ressource')}
@@ -277,12 +290,19 @@ const CreatePlanForm: FC = ({
{resources.map((resource) => (
onChangeResource(resource.value)}
>
-
- {resource.label}
+
+
+ {resource.label}
+
))}
@@ -290,27 +310,25 @@ const CreatePlanForm: FC = ({
{t('select_model')}
{isInstance && (
-
-
-
- {tabsList.map((tab) => (
- setInstanceCategory(tab.technicalName)}
- >
- {tab.label}
-
- ))}
-
-
+
+
+ {tabsList.map((tab) => (
+ setInstanceCategory(tab.technicalName)}
+ >
+ {tab.label}
+
+ ))}
+
)}
{t(getDescriptionInstanceKey(instanceCategory))}
{!isTechnicalInfoLoading ? (
-
+
{currentInstanceSelected.technical?.map(({ name, technical }) => (
= ({
))}
) : (
-
+
)}
{t('select_quantity')}
-
+
{t('select_quantity_description')}
-
-
- {t('quantity_label')}
-
-
- setQuantity(quantity - 1)}
- onPlusClick={() => {
- if (quantity < MAX_QUANTITY) {
- setQuantity(quantity + 1);
- }
- }}
- onChangeQuantity={onChangeQuantity}
- />
-
-
-
-
+
+ {t('quantity_label')}
+
+
+
+
{t(
isInstance
? 'quantity_banner_instance'
: 'quantity_banner_rancher',
)}
{isInstance && (
-
- {t('quantity_banner_instance_link')}
-
-
+ target="_blank"
+ icon={ODS_ICON_NAME.externalLink}
+ label={t('quantity_banner_instance_link')}
+ />
)}
-
-
+
+
{t('select_commitment')}
@@ -403,77 +399,54 @@ const CreatePlanForm: FC = ({
);
})
) : (
-
+
)}
{t('choose_name')}
- ,
- ) => setPlanName(e.target.value as string)}
+ onOdsChange={(e) => setPlanName(e.target.value as string)}
/>
- {t('input_name_rules')}
+
+ {t('input_name_rules')}
+
-
- setIsLegalChecked(!isLegalChecked)}
- hasFocus={true}
- >
-
- {t('legal_checkbox')}
-
-
-
-
+ setIsLegalChecked(!isLegalChecked)}
+ />
+
+ {t('legal_checkbox')}
+
+
-
-
+ {
- trackClick({
- location: PageLocation.funnel,
- buttonType: ButtonType.button,
- actionType: 'action',
- actions: [`add_savings_plan::cancell`],
- });
- navigate('..');
- }}
- >
- {t('cta_cancel')}
-
- navigate('..')}
+ />
+
+
- {t('cta_plan')}
-
+ />
);
@@ -486,7 +459,11 @@ export const CreatePlanFormContainer = ({
}: {
isDiscoveryProject: boolean;
}) => {
- const { t } = useTranslation('create');
+ const { environment } = useContext(ShellContext);
+ const locale = environment.getUserLocale();
+
+ const { t } = useTranslation(['create', 'listing']);
+ const { addSuccess } = useNotifications();
const [instanceCategory, setInstanceCategory] = useState<
InstanceTechnicalName
@@ -515,7 +492,20 @@ export const CreatePlanFormContainer = ({
}
}, [technicalList]);
- const { mutate: onCreatePlan } = useSavingsPlanCreate();
+ const handleCreateSavingsPlanSuccess = useCallback(
+ (data: { startDate: string }) => {
+ addSuccess(
+ t('listing:banner_create_sp', {
+ startDate: toLocalDateUTC(data.startDate, locale),
+ }),
+ );
+ },
+ [addSuccess, t, locale],
+ );
+
+ const { mutate: onCreatePlan } = useSavingsPlanCreate(
+ handleCreateSavingsPlanSuccess,
+ );
const sortedPriceByDuration = [...pricingByDuration].sort(
(a, b) => a.duration - b.duration,
diff --git a/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.test.tsx b/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.test.tsx
index 8f429a7ba9ad..b41d6bc55664 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.test.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.test.tsx
@@ -1,9 +1,8 @@
import '@testing-library/jest-dom';
-import { screen, waitFor } from '@testing-library/react';
+import { waitFor } from '@testing-library/react';
import { HttpResponse, http } from 'msw';
import { setupServer } from 'msw/node';
import React from 'react';
-import { vi } from 'vitest';
import { render } from '@/utils/testProvider';
import LegalLinks from './LegalLinks';
import { SavingsPlanContract } from '@/types';
@@ -24,15 +23,12 @@ const MOCK_CONTRACTS: SavingsPlanContract[] = [
];
const server = setupServer(
- http.get('/engine/apiv6/services?resourceName=undefined', ({ request }) => {
+ http.get('/engine/apiv6/services?resourceName=undefined', () => {
return HttpResponse.json([123]);
}),
- http.get(
- '/engine/apiv6/services/123/savingsPlans/contracts',
- ({ request }) => {
- return HttpResponse.json(MOCK_CONTRACTS);
- },
- ),
+ http.get('/engine/apiv6/services/123/savingsPlans/contracts', () => {
+ return HttpResponse.json(MOCK_CONTRACTS);
+ }),
);
beforeAll(() => server.listen());
@@ -41,11 +37,16 @@ afterAll(() => server.close());
describe('LegalLinks', async () => {
it('renders the legal links correctly', async () => {
- render(
);
+ const { container } = render(
);
await waitFor(() => {
- expect(screen.getByText(MOCK_CONTRACTS[0].name)).toBeInTheDocument();
- expect(screen.getByText(MOCK_CONTRACTS[1].name)).toBeInTheDocument();
+ MOCK_CONTRACTS.forEach((contract) => {
+ expect(
+ container.querySelector(
+ `[label='${contract.name}'][href='${contract.url}']`,
+ ),
+ ).toBeInTheDocument();
+ });
});
});
});
diff --git a/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.tsx b/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.tsx
index 01846d36b38b..7ce160b503b7 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/LegalLinks/LegalLinks.tsx
@@ -1,19 +1,25 @@
-import { LinkType, Links } from '@ovh-ux/manager-react-components';
+import { Links, LinkType } from '@ovh-ux/manager-react-components';
import React from 'react';
-import { OdsHTMLAnchorElementTarget } from '@ovhcloud/ods-common-core';
import { useSavingsPlanContract } from '@/hooks/useSavingsPlan';
-const LegalLinks = () => {
+type TLegalLinksProps = {
+ className?: string;
+};
+
+const LegalLinks: React.FC
= ({ className = '' }) => {
const { data = [] } = useSavingsPlanContract();
return (
<>
{data.map((link) => (
))}
>
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Loading/Loading.tsx b/packages/manager/apps/pci-savings-plan/src/components/Loading/Loading.tsx
index bb74a4fa8e15..863da5efde54 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Loading/Loading.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Loading/Loading.tsx
@@ -1,10 +1,10 @@
import React from 'react';
-import { OsdsSpinner } from '@ovhcloud/ods-components/react';
+import { OdsSpinner } from '@ovhcloud/ods-components/react';
export default function Loading() {
return (
-
+
);
}
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Modal/RenewModal.tsx b/packages/manager/apps/pci-savings-plan/src/components/Modal/RenewModal.tsx
index 4dc4e3338c75..0cb4333dc292 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Modal/RenewModal.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Modal/RenewModal.tsx
@@ -1,15 +1,6 @@
import React from 'react';
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import {
- ODS_BUTTON_VARIANT,
- ODS_TEXT_LEVEL,
- ODS_TEXT_SIZE,
-} from '@ovhcloud/ods-components';
-import {
- OsdsButton,
- OsdsModal,
- OsdsText,
-} from '@ovhcloud/ods-components/react';
+import { ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
+import { OdsButton, OdsModal, OdsText } from '@ovhcloud/ods-components/react';
import { useTranslation } from 'react-i18next';
import { SavingsPlanPlanedChangeStatus } from '@/types/api.type';
@@ -26,43 +17,30 @@ export default function RenewModal({
}: Readonly) {
const { t } = useTranslation('renew');
+ const isReactivate =
+ periodEndAction === SavingsPlanPlanedChangeStatus.REACTIVATE;
return (
-
-
-
-
- {t(
- periodEndAction === SavingsPlanPlanedChangeStatus.REACTIVATE
- ? 'message_deactivate'
- : 'message_activate',
- )}
-
-
-
-
- {t('buttons_cancel')}
-
-
- {t(
- periodEndAction === SavingsPlanPlanedChangeStatus.REACTIVATE
- ? 'buttons_deactivate'
- : 'buttons_activate',
- )}
-
-
+
+ {t('title')}
+
+
+
+ {t(isReactivate ? 'message_deactivate' : 'message_activate')}
+
+
+
+
+
+
+
+
);
}
diff --git a/packages/manager/apps/pci-savings-plan/src/components/PlannedChangeStatusChip/PlannedChangeStatusChip.tsx b/packages/manager/apps/pci-savings-plan/src/components/PlannedChangeStatusChip/PlannedChangeStatusChip.tsx
index b96c8d943df9..d9fea74111af 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/PlannedChangeStatusChip/PlannedChangeStatusChip.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/PlannedChangeStatusChip/PlannedChangeStatusChip.tsx
@@ -1,6 +1,5 @@
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import { ODS_SPINNER_SIZE } from '@ovhcloud/ods-components';
-import { OsdsChip, OsdsSpinner } from '@ovhcloud/ods-components/react';
+import { ODS_SPINNER_SIZE, ODS_BADGE_COLOR } from '@ovhcloud/ods-components';
+import { OdsBadge, OdsSpinner } from '@ovhcloud/ods-components/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SavingsPlanPlanedChangeStatus } from '@/types/api.type';
@@ -8,21 +7,19 @@ import { SavingsPlanPlanedChangeStatus } from '@/types/api.type';
const PlannedChangeStatusChip = ({ label }: { label: string }) => {
const { t } = useTranslation('listing');
const colorByProductStatus: {
- [key in SavingsPlanPlanedChangeStatus]: ODS_THEME_COLOR_INTENT;
+ [key in SavingsPlanPlanedChangeStatus]: ODS_BADGE_COLOR;
} = {
- [SavingsPlanPlanedChangeStatus.REACTIVATE]: ODS_THEME_COLOR_INTENT.success,
- [SavingsPlanPlanedChangeStatus.TERMINATE]: ODS_THEME_COLOR_INTENT.error,
+ [SavingsPlanPlanedChangeStatus.REACTIVATE]: ODS_BADGE_COLOR.success,
+ [SavingsPlanPlanedChangeStatus.TERMINATE]: ODS_BADGE_COLOR.critical,
};
return label ? (
-
- {t(label.toLowerCase() as SavingsPlanPlanedChangeStatus)}
-
+ />
) : (
-
+
);
};
diff --git a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx
deleted file mode 100644
index c5d766d5f81d..000000000000
--- a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.test.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import React from 'react';
-import { vi, describe, it, expect } from 'vitest';
-import { screen, fireEvent } from '@testing-library/react';
-import QuantitySelector, { MAX_QUANTITY } from './QuantitySelector';
-import '@testing-library/jest-dom';
-
-import { render } from '@/utils/testProvider';
-
-const defaultProps = {
- quantity: 1,
- onMinusClick: vi.fn(),
- onPlusClick: vi.fn(),
- onChangeQuantity: vi.fn(),
-};
-
-const setupSpecTest = async (props = defaultProps) =>
- render( );
-
-describe('QuantitySelector', () => {
- it('should render the quantity input with the correct value', async () => {
- await setupSpecTest();
- const input = screen.getByTestId('input-quantity');
- expect(input).toHaveValue(defaultProps.quantity);
- });
-
- it('should call onMinusClick when minus button is clicked', async () => {
- await setupSpecTest();
- const minusButton = screen.getByTestId('minus-button');
- fireEvent.click(minusButton);
- expect(defaultProps.onMinusClick).toHaveBeenCalled();
- });
-
- it('should call onPlusClick when plus button is clicked', async () => {
- await setupSpecTest();
- const plusButton = screen.getByTestId('plus-button');
- fireEvent.click(plusButton);
- expect(defaultProps.onPlusClick).toHaveBeenCalled();
- });
-
- it('should call onChangeQuantity with the correct value when input changes', async () => {
- await setupSpecTest();
- const input = screen.getByTestId('input-quantity');
- fireEvent.change(input, { target: { value: '5' } });
- expect(defaultProps.onChangeQuantity).toHaveBeenCalledWith(5);
- });
- it('should not call onChangeQuantity with a value greater than MAX_QUANTITY', async () => {
- await setupSpecTest();
- const input = screen.getByTestId('input-quantity');
- fireEvent.change(input, { target: { value: MAX_QUANTITY + 1 } });
- expect(defaultProps.onChangeQuantity).not.toHaveBeenCalledWith(
- MAX_QUANTITY + 1,
- );
- });
-});
diff --git a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx b/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx
deleted file mode 100644
index f5bee8c1093c..000000000000
--- a/packages/manager/apps/pci-savings-plan/src/components/QuantitySelector/QuantitySelector.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import {
- ODS_BUTTON_SIZE,
- ODS_BUTTON_TYPE,
- ODS_BUTTON_VARIANT,
- ODS_ICON_NAME,
- ODS_ICON_SIZE,
- ODS_INPUT_SIZE,
- ODS_INPUT_TYPE,
-} from '@ovhcloud/ods-components';
-import {
- OsdsButton,
- OsdsIcon,
- OsdsInput,
- OsdsQuantity,
-} from '@ovhcloud/ods-components/react';
-import React from 'react';
-
-type QuantitySelectorProps = {
- quantity: number;
- onMinusClick: () => void;
- onPlusClick: () => void;
- onChangeQuantity: (v?: number) => void;
-};
-
-export const MAX_QUANTITY = 1000;
-const QuantitySelector: React.FC = ({
- quantity,
- onMinusClick,
- onPlusClick,
- onChangeQuantity,
-}) => {
- const isDisabledPlus = quantity >= MAX_QUANTITY;
-
- return (
-
-
-
-
- {
- const value = Number(e.detail.value);
- if (value > 0 && value <= MAX_QUANTITY) {
- onChangeQuantity(value);
- }
- }}
- size={ODS_INPUT_SIZE.md}
- >
-
-
-
-
- );
-};
-
-export default QuantitySelector;
diff --git a/packages/manager/apps/pci-savings-plan/src/components/SimpleTile/SimpleTile.tsx b/packages/manager/apps/pci-savings-plan/src/components/SimpleTile/SimpleTile.tsx
index 2412cc602ef7..1eb796de13cf 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/SimpleTile/SimpleTile.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/SimpleTile/SimpleTile.tsx
@@ -1,28 +1,27 @@
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import { ODS_TILE_VARIANT } from '@ovhcloud/ods-components';
-import { OsdsTile } from '@ovhcloud/ods-components/react';
+import { OdsCard } from '@ovhcloud/ods-components/react';
+import clsx from 'clsx';
import React from 'react';
const SimpleTile: React.FC void;
isActive?: boolean;
-}>> = ({ children, onClick, isActive }) => (
- > = ({ children, onClick, isActive, className }) => (
+
{children}
-
+
);
export default SimpleTile;
diff --git a/packages/manager/apps/pci-savings-plan/src/components/StatusChip/StatusChip.tsx b/packages/manager/apps/pci-savings-plan/src/components/StatusChip/StatusChip.tsx
index 40ce02e05c87..387a1ca1685a 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/StatusChip/StatusChip.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/StatusChip/StatusChip.tsx
@@ -1,6 +1,5 @@
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import { ODS_SPINNER_SIZE } from '@ovhcloud/ods-components';
-import { OsdsChip, OsdsSpinner } from '@ovhcloud/ods-components/react';
+import { ODS_SPINNER_SIZE, ODS_BADGE_COLOR } from '@ovhcloud/ods-components';
+import { OdsBadge, OdsSpinner } from '@ovhcloud/ods-components/react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SavingsPlanStatus } from '@/types/api.type';
@@ -8,19 +7,20 @@ import { SavingsPlanStatus } from '@/types/api.type';
const StatusChip = ({ label }: { label: string }) => {
const { t } = useTranslation('listing');
const colorByProductStatus: {
- [key in SavingsPlanStatus]: ODS_THEME_COLOR_INTENT;
+ [key in SavingsPlanStatus]: ODS_BADGE_COLOR;
} = {
- [SavingsPlanStatus.ACTIVE]: ODS_THEME_COLOR_INTENT.success,
- [SavingsPlanStatus.PENDING]: ODS_THEME_COLOR_INTENT.warning,
- [SavingsPlanStatus.TERMINATED]: ODS_THEME_COLOR_INTENT.error,
+ [SavingsPlanStatus.ACTIVE]: ODS_BADGE_COLOR.success,
+ [SavingsPlanStatus.PENDING]: ODS_BADGE_COLOR.warning,
+ [SavingsPlanStatus.TERMINATED]: ODS_BADGE_COLOR.critical,
};
return label ? (
-
- {t(label.toLowerCase() as SavingsPlanStatus)}
-
+
) : (
-
+
);
};
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Table/ActionsCell.tsx b/packages/manager/apps/pci-savings-plan/src/components/Table/ActionsCell.tsx
index 0e4173bc39fe..8af7c5c80a75 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Table/ActionsCell.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Table/ActionsCell.tsx
@@ -1,20 +1,13 @@
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
+import { useNavigate } from 'react-router-dom';
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
import {
ODS_BUTTON_SIZE,
- ODS_BUTTON_TYPE,
ODS_BUTTON_VARIANT,
ODS_ICON_NAME,
- ODS_ICON_SIZE,
} from '@ovhcloud/ods-components';
-import {
- OsdsButton,
- OsdsIcon,
- OsdsMenu,
- OsdsMenuItem,
-} from '@ovhcloud/ods-components/react';
+import { OdsButton, OdsPopover } from '@ovhcloud/ods-components/react';
import { usePciUrl } from '@ovh-ux/manager-pci-common';
import {
ButtonType,
@@ -36,6 +29,7 @@ interface SavingsPlanActionsCell {
}
const MenuItems = ({
+ id,
status,
flavor,
onClickEdit,
@@ -43,6 +37,7 @@ const MenuItems = ({
periodEndAction,
pciUrl,
}: {
+ id: string;
status: SavingsPlanStatus;
flavor: string;
onClickEdit: () => void;
@@ -53,49 +48,37 @@ const MenuItems = ({
const { t } = useTranslation('listing');
const { trackClick } = useOvhTracking();
+ const navigate = useNavigate();
// We don't have a better way to check that, api return only a specific code and not an id related to scope (instance, rancher),
// So if we have number in the flavor (b3-8, c3-16) it's an instance else it's a Rancher
const isInstance = useMemo(() => /\d/.test(flavor), [flavor]);
return (
- <>
-
-
+
+
-
- {t('edit')}
-
-
-
-
- {status !== SavingsPlanStatus.TERMINATED && (
-
-
+ {status !== SavingsPlanStatus.TERMINATED && (
+
-
-
- {periodEndAction === SavingsPlanPlanedChangeStatus.TERMINATE
- ? t('enableAutoRenew')
- : t('disableAutoRenew')}
-
-
-
-
- )}
+ />
+ )}
-
-
-
- {t(isInstance ? 'order_instance' : 'order_rancher')}
-
-
-
- >
+ />
+
+
);
};
@@ -135,24 +116,18 @@ export default function ActionsCell({
const onClickEdit = useCallback(() => onClickEditName(id), [id]);
return (
-
-
-
+
+
-
+
-
+ >
);
}
diff --git a/packages/manager/apps/pci-savings-plan/src/components/Table/TableContainer.tsx b/packages/manager/apps/pci-savings-plan/src/components/Table/TableContainer.tsx
index 0305f5a13eec..cb71d43db672 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/Table/TableContainer.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/Table/TableContainer.tsx
@@ -1,11 +1,6 @@
import React, { useContext, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import {
- useLocation,
- useNavigate,
- useParams,
- useSearchParams,
-} from 'react-router-dom';
+import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { ShellContext } from '@ovh-ux/manager-react-shell-client';
import {
diff --git a/packages/manager/apps/pci-savings-plan/src/components/TileTechnicalInfo/TileTechnicalInfo.tsx b/packages/manager/apps/pci-savings-plan/src/components/TileTechnicalInfo/TileTechnicalInfo.tsx
index a70fb32f9ff8..00e402827141 100644
--- a/packages/manager/apps/pci-savings-plan/src/components/TileTechnicalInfo/TileTechnicalInfo.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/components/TileTechnicalInfo/TileTechnicalInfo.tsx
@@ -1,10 +1,5 @@
import React from 'react';
-import {
- ODS_THEME_COLOR_HUE,
- ODS_THEME_COLOR_INTENT,
- ODS_THEME_TYPOGRAPHY_SIZE,
-} from '@ovhcloud/ods-common-theming';
-import { OsdsText } from '@ovhcloud/ods-components/react';
+import { OdsText } from '@ovhcloud/ods-components/react';
import { useTranslation } from 'react-i18next';
import SimpleTile from '../SimpleTile/SimpleTile';
import { formatTechnicalInfo } from '@/utils/formatter/formatter';
@@ -18,30 +13,27 @@ const DisplayTechnicalInfo = ({
const { memory, cpu, storage, bandwidth } = technical;
return (
<>
-
+
{t('resource_model_characteristics_gb', {
value: memory.size,
})}
-
-
+
+
{t('resource_model_characteristics_cpu', {
cores: cpu.cores,
ghz: cpu.frequency,
})}
-
-
+
+
{t('resource_model_characteristics_disk', {
value: storage.disks[0].capacity,
})}
-
-
+
+
{t('resource_model_characteristics_mbits', {
value: bandwidth.level,
})}
-
+
>
);
};
@@ -61,14 +53,9 @@ export const TileTechnicalInfo: React.FC = ({
}) => (
-
- {name}
-
+
+ {name}
+
{technical && technical.bandwidth && (
)}
diff --git a/packages/manager/apps/pci-savings-plan/src/hooks/useSavingsPlan.tsx b/packages/manager/apps/pci-savings-plan/src/hooks/useSavingsPlan.tsx
index 973476f5d843..14626d9f452b 100644
--- a/packages/manager/apps/pci-savings-plan/src/hooks/useSavingsPlan.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/hooks/useSavingsPlan.tsx
@@ -8,6 +8,7 @@ import {
} from '@ovh-ux/manager-react-shell-client';
import { useServices } from './useService';
import {
+ UseSavingsPlanParams,
SavingsPlanContract,
SavingsPlanPlanedChangeStatus,
SavingsPlanService,
@@ -105,12 +106,18 @@ export const getMutationKeySPChangePeriod = (
serviceId: number,
) => ['savings-plan', serviceId, 'change-period', savingsPlanId];
-export const useSavingsPlanChangePeriod = (savingsPlanId: string) => {
+export const useSavingsPlanChangePeriod = ({
+ savingsPlanId,
+ onSuccess,
+}: UseSavingsPlanParams) => {
const { refetch } = useSavingsPlan();
const serviceId = useServiceId();
return useMutation({
- onSuccess: () => refetch(),
+ onSuccess: async () => {
+ onSuccess?.();
+ refetch();
+ },
mutationKey: getMutationKeySPChangePeriod(savingsPlanId, serviceId),
mutationFn: ({
periodEndAction,
@@ -136,19 +143,27 @@ export const getMutationKeyCreateSavingsPlan = (serviceId: number) => [
'create',
];
-export const useSavingsPlanEditName = (savingsPlanId: string) => {
+export const useSavingsPlanEditName = ({
+ savingsPlanId,
+ onSuccess,
+}: UseSavingsPlanParams) => {
const { refetch } = useSavingsPlan();
const serviceId = useServiceId();
return useMutation({
- onSuccess: () => refetch(),
mutationKey: getMutationKeySPEditName(savingsPlanId, serviceId),
mutationFn: ({ displayName }: { displayName: string }) =>
putSubscribedSavingsPlanEditName(serviceId, savingsPlanId, displayName),
+ onSuccess: async () => {
+ onSuccess?.();
+ refetch();
+ },
});
};
-export const useSavingsPlanCreate = () => {
+export const useSavingsPlanCreate = (
+ onSuccess?: (data: SavingsPlanService) => void,
+) => {
const { refetch } = useSavingsPlan();
const { trackClick } = useOvhTracking();
const serviceId = useServiceId();
@@ -157,7 +172,6 @@ export const useSavingsPlanCreate = () => {
return useMutation({
onSuccess: async (res) => {
- const { data } = await refetch();
trackClick({
location: PageLocation.funnel,
buttonType: ButtonType.button,
@@ -166,8 +180,9 @@ export const useSavingsPlanCreate = () => {
`add_savings_plan::confirm::savings_plan_created_${res.period}_${res.flavor}_${res.model}_${res.size}`,
],
});
-
- if (data?.length) {
+ const { data: refetchData } = await refetch();
+ if (refetchData?.length) {
+ onSuccess?.(refetchData[0]);
navigate(getSavingsPlansUrl(projectId));
}
},
diff --git a/packages/manager/apps/pci-savings-plan/src/index.scss b/packages/manager/apps/pci-savings-plan/src/index.scss
index 65dd5f63a7df..565a6e57a720 100644
--- a/packages/manager/apps/pci-savings-plan/src/index.scss
+++ b/packages/manager/apps/pci-savings-plan/src/index.scss
@@ -1 +1,47 @@
@tailwind utilities;
+@import '@ovhcloud/ods-themes/default';
+
+html {
+ font-family: var(--ods-font-family-default);
+}
+
+.input-at::part(input) {
+ text-align: center;
+}
+
+ods-text::part(text) {
+ width: 100%;
+}
+
+ods-button.action-menu-item::part(button) {
+ width: 100%;
+ justify-content: left;
+}
+
+ods-modal::part(dialog) {
+ max-height: 100vh;
+}
+
+// tag doesn't seems centered if line is bigger
+ods-tag.org-tag::part(tag) {
+ transform: translateY(-10%);
+}
+
+.dns-field {
+ width: 100%;
+
+ &::part(text) {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ }
+
+ ods-clipboard {
+ flex: 1;
+ width: 100%;
+
+ &::part(input) {
+ width: 100%;
+ }
+ }
+}
diff --git a/packages/manager/apps/pci-savings-plan/src/index.tsx b/packages/manager/apps/pci-savings-plan/src/index.tsx
index e77e222d08b2..dba52b09bb05 100644
--- a/packages/manager/apps/pci-savings-plan/src/index.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/index.tsx
@@ -3,7 +3,6 @@ import {
initI18n,
initShellContext,
} from '@ovh-ux/manager-react-shell-client';
-import '@ovhcloud/ods-theme-blue-jeans/dist/index.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
diff --git a/packages/manager/apps/pci-savings-plan/src/pages/listing/edit-name/index.tsx b/packages/manager/apps/pci-savings-plan/src/pages/listing/edit-name/index.tsx
index 508a387eb7c0..5b7f2eea2d9c 100644
--- a/packages/manager/apps/pci-savings-plan/src/pages/listing/edit-name/index.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/pages/listing/edit-name/index.tsx
@@ -1,46 +1,64 @@
-import React, { Suspense } from 'react';
+import React, { startTransition, Suspense } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { UpdateNameModal } from '@ovh-ux/manager-react-components';
+import {
+ UpdateNameModal,
+ useNotifications,
+} from '@ovh-ux/manager-react-components';
import { useSavingsPlan, useSavingsPlanEditName } from '@/hooks/useSavingsPlan';
import { REGEX } from '@/utils/savingsPlan';
-const EditNamePage = () => {
- const { t } = useTranslation('edit-name');
- const { t: tCreate } = useTranslation('create');
+const EditNameChildren = () => {
+ const { t } = useTranslation(['edit-name', 'listing', 'create']);
const navigate = useNavigate();
const { savingsPlanId } = useParams();
+ const { addSuccess } = useNotifications();
const { data: savingsPlan } = useSavingsPlan();
- const { mutate: editName } = useSavingsPlanEditName(savingsPlanId);
+ const { mutate: editName } = useSavingsPlanEditName({
+ savingsPlanId,
+ onSuccess: () => {
+ addSuccess(t('listing:banner_edit_name'));
+ },
+ });
const [searchParams] = useSearchParams();
- const currentPlan = savingsPlan.find((plan) => plan.id === savingsPlanId);
+ const currentPlan = savingsPlan?.find((plan) => plan.id === savingsPlanId);
return (
-
+ <>
{currentPlan ? (
navigate({ pathname: '..', search: searchParams.toString() })
}
defaultValue={currentPlan?.displayName || ''}
updateDisplayName={(displayName) => {
- editName({
- displayName,
+ startTransition(() => {
+ editName({
+ displayName,
+ });
+ navigate({ pathname: '..', search: searchParams.toString() });
});
- navigate({ pathname: '..', search: searchParams.toString() });
}}
- headline={t('title')}
+ headline={t('edit-name:title')}
pattern={REGEX}
- description={t('description')}
- patternMessage={tCreate('input_name_rules')}
+ description={t('edit-name:description')}
+ patternMessage={t('create:input_name_rules')}
/>
) : (
<>>
)}
+ >
+ );
+};
+const EditNamePage = () => {
+ return (
+
+
);
};
diff --git a/packages/manager/apps/pci-savings-plan/src/pages/listing/index.tsx b/packages/manager/apps/pci-savings-plan/src/pages/listing/index.tsx
index e51adaeccf5f..95b213965870 100644
--- a/packages/manager/apps/pci-savings-plan/src/pages/listing/index.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/pages/listing/index.tsx
@@ -1,38 +1,32 @@
-import React, { useContext, useEffect, useState } from 'react';
+import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next';
-import { Outlet, useHref, useNavigate, useParams } from 'react-router-dom';
-import { MutationStatus, useMutationState } from '@tanstack/react-query';
+import { Outlet, useNavigate, useParams } from 'react-router-dom';
+
+import { ODS_BUTTON_SIZE, ODS_BUTTON_VARIANT } from '@ovhcloud/ods-components';
+import { OdsButton, OdsText } from '@ovhcloud/ods-components/react';
-import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
-import {
- ODS_BUTTON_SIZE,
- ODS_BUTTON_VARIANT,
- ODS_ICON_NAME,
- ODS_ICON_SIZE,
- ODS_MESSAGE_TYPE,
-} from '@ovhcloud/ods-components';
import {
- OsdsButton,
- OsdsIcon,
- OsdsMessage,
- OsdsText,
-} from '@ovhcloud/ods-components/react';
-import { Title } from '@ovh-ux/manager-react-components';
+ Notifications,
+ Title,
+ useNotifications,
+} from '@ovh-ux/manager-react-components';
import {
ButtonType,
PageLocation,
useOvhTracking,
- ShellContext,
} from '@ovh-ux/manager-react-shell-client';
import TableContainer from '@/components/Table/TableContainer';
-import {
- getMutationKeyCreateSavingsPlan,
- useSavingsPlan,
- useServiceId,
-} from '@/hooks/useSavingsPlan';
+import { useSavingsPlan } from '@/hooks/useSavingsPlan';
import { SavingsPlanService } from '@/types';
-import { toLocalDateUTC } from '@/utils/formatter/date';
+
+interface ListingTablePageProps {
+ data: SavingsPlanService[];
+ refetchSavingsPlans: () => void;
+}
+interface ListingProps {
+ refetchSavingsPlans: () => void;
+}
export const formatDateString = (dateString: string, locale?: string) => {
const date = new Date(dateString);
@@ -47,151 +41,48 @@ export const formatDateString = (dateString: string, locale?: string) => {
: '-';
};
-const Banner = ({ message }: { message: string }) => {
- const [showBanner, setShowBanner] = useState(true);
-
- useEffect(() => {
- if (message) {
- setShowBanner(true);
- }
- }, [message]);
- return (
- showBanner && (
- setShowBanner(false)}
- >
-
- {message}
-
-
- )
- );
-};
-
-export interface ListingProps {
- data: SavingsPlanService[];
- refetchSavingsPlans: () => void;
-}
-
-type MutationInfo = {
- submittedAt: number;
- error?: {
- code: string;
- };
- status: MutationStatus;
- data?: Data;
-};
-
-const getMutationFilters = (filters: (string | number)[]) => ({
+export const getMutationFilters = (filters: (string | number)[]) => ({
filters: { mutationKey: filters },
});
-const ListingTablePage: React.FC = ({
+const ListingTablePage: React.FC = ({
data,
refetchSavingsPlans,
}) => {
const { t } = useTranslation('listing');
- const { environment } = useContext(ShellContext);
- const locale = environment.getUserLocale();
const { trackClick } = useOvhTracking();
- const hrefDashboard = useHref('');
- const serviceId = useServiceId();
- const mutationSPChangePeriod = useMutationState<
- MutationInfo & {
- variables: {
- periodEndAction: 'REACTIVATE' | 'ACTIVATE';
- };
- }
- >(getMutationFilters(['savings-plan', serviceId, 'change-period']));
-
- const mutationSPEditName = useMutationState(
- getMutationFilters(['savings-plan', serviceId, 'edit-name']),
- );
-
- const mutationSpCreate = useMutationState>(
- getMutationFilters(getMutationKeyCreateSavingsPlan(serviceId)),
- );
-
- const lastMutationEditName =
- mutationSPEditName[mutationSPEditName.length - 1];
- const lastMutationChangePeriod =
- mutationSPChangePeriod[mutationSPChangePeriod.length - 1];
- const lastMutationSpCreate = mutationSpCreate[mutationSpCreate.length - 1];
-
- const renewBannerMessage = t(
- lastMutationChangePeriod?.variables.periodEndAction === 'REACTIVATE'
- ? 'banner_renew_activate'
- : 'banner_renew_deactivate',
- {
- planName: lastMutationChangePeriod?.data?.displayName,
- endDate: lastMutationChangePeriod?.data?.endDate,
- },
- );
+ const navigate = useNavigate();
+ const { projectId } = useParams();
+ const { clearNotifications } = useNotifications();
+
+ const handleClick = () => {
+ trackClick({
+ location: PageLocation.page,
+ buttonType: ButtonType.button,
+ actionType: 'navigation',
+ actions: ['add_savings_plan'],
+ });
+ clearNotifications();
+ navigate(`/pci/projects/${projectId}/savings-plan/new`);
+ };
return (
<>
{t('title')}
- {
- trackClick({
- location: PageLocation.page,
- buttonType: ButtonType.button,
- actionType: 'navigation',
- actions: ['add_savings_plan'],
- });
- }}
- >
-
-
- {t('createSavingsPlan')}
-
-
+ variant={ODS_BUTTON_VARIANT.outline}
+ onClick={handleClick}
+ label={t('createSavingsPlan')}
+ />
-
+
{t('informationMessage')}
-
- {mutationSPChangePeriod.length > 0 && (
-
- )}
- {mutationSpCreate.length > 0 && !lastMutationSpCreate.error?.code && (
-
- )}
-
- {mutationSPEditName.length > 0 && (
-
- )}
+
+
>
);
diff --git a/packages/manager/apps/pci-savings-plan/src/pages/listing/renew-modal/index.tsx b/packages/manager/apps/pci-savings-plan/src/pages/listing/renew-modal/index.tsx
index b44d327fbae5..f0794eb761cd 100644
--- a/packages/manager/apps/pci-savings-plan/src/pages/listing/renew-modal/index.tsx
+++ b/packages/manager/apps/pci-savings-plan/src/pages/listing/renew-modal/index.tsx
@@ -1,5 +1,7 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
+import { useNotifications } from '@ovh-ux/manager-react-components';
+import { useTranslation } from 'react-i18next';
import Errors from '@/components/Error/Error';
import RenewModal from '@/components/Modal/RenewModal';
import {
@@ -11,18 +13,37 @@ import { SavingsPlanPlanedChangeStatus } from '@/types/api.type';
const RenewModalPage = () => {
const navigate = useNavigate();
const { savingsPlanId } = useParams();
-
const { data: savingsPlan, error, isError } = useSavingsPlan();
- const { mutate: changePeriod } = useSavingsPlanChangePeriod(savingsPlanId);
+ const { t } = useTranslation('listing');
+ const { addSuccess } = useNotifications();
+
+ const currentPlan = useMemo(
+ () => savingsPlan?.find((p) => p.id === savingsPlanId),
+ [savingsPlan, savingsPlanId],
+ );
+
+ const periodEndAction = currentPlan?.periodEndAction === 'REACTIVATE';
+
+ const onSuccess = () => {
+ addSuccess(
+ t(periodEndAction ? 'banner_renew_deactivate' : 'banner_renew_activate', {
+ planName: currentPlan.displayName,
+ endDate: currentPlan.endDate,
+ }),
+ );
+ };
+
+ const { mutate: changePeriod } = useSavingsPlanChangePeriod({
+ savingsPlanId,
+ onSuccess,
+ });
+
const [searchParams] = useSearchParams();
if (isError || error) {
return ;
}
- const currentPlan = savingsPlan?.find((plan) => plan.id === savingsPlanId);
- const periodEndAction = currentPlan?.periodEndAction === 'REACTIVATE';
-
const goToPrevious = () =>
navigate({ pathname: '..', search: searchParams.toString() });
diff --git a/packages/manager/apps/pci-savings-plan/src/types/api.type.ts b/packages/manager/apps/pci-savings-plan/src/types/api.type.ts
index 153fbad73b51..187d42229532 100644
--- a/packages/manager/apps/pci-savings-plan/src/types/api.type.ts
+++ b/packages/manager/apps/pci-savings-plan/src/types/api.type.ts
@@ -1,3 +1,5 @@
+import { MutationStatus } from '@tanstack/react-query';
+
export interface PciProject {
project_id: string;
projectName: string;
@@ -65,3 +67,17 @@ export interface SavingsPlanService {
startDate: string;
status: SavingsPlanStatus;
}
+
+export type MutationInfo = {
+ submittedAt: number;
+ error?: {
+ code: string;
+ };
+ status: MutationStatus;
+ data?: T;
+};
+
+export type UseSavingsPlanParams = {
+ savingsPlanId: string;
+ onSuccess?: () => void;
+};
diff --git a/packages/manager/apps/pci-savings-plan/vitest.config.js b/packages/manager/apps/pci-savings-plan/vitest.config.js
index c8a1ca9993bb..0b1799e6e234 100644
--- a/packages/manager/apps/pci-savings-plan/vitest.config.js
+++ b/packages/manager/apps/pci-savings-plan/vitest.config.js
@@ -6,6 +6,11 @@ import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
+ server: {
+ deps: {
+ inline: ['clsx'],
+ },
+ },
globals: true,
environment: 'jsdom',
coverage: {
diff --git a/yarn.lock b/yarn.lock
index 017b6bf43ab0..a1b7e6b64732 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5792,6 +5792,17 @@
date-fns "^3.6.0"
lodash.isequal "^4.5.0"
+"@ovh-ux/manager-pci-common@^1.0.4":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@ovh-ux/manager-pci-common/-/manager-pci-common-1.0.4.tgz#9fb79f247208a350d9de6f70178677ee00432ad3"
+ integrity sha512-QKItmyT2Uu4Gpq6RbGm+vsTcN7eSZamvLTswdfkNFH3YHHTx47a1PofCjgsUXlBZ+YD7H0bZl3CVBexGY7T8iw==
+ dependencies:
+ "@ovh-ux/manager-core-utils" "^0.3.0"
+ "@ovh-ux/manager-tailwind-config" "^0.2.1"
+ clsx "2.1.1"
+ date-fns "^3.6.0"
+ lodash.isequal "^4.5.0"
+
"@ovh-ux/manager-react-components@^1.41.1", "@ovh-ux/manager-react-components@^1.41.2":
version "1.41.2"
resolved "https://registry.yarnpkg.com/@ovh-ux/manager-react-components/-/manager-react-components-1.41.2.tgz#087cacbbff37c594201851f5f27d2a0c524545b5"