diff --git a/packages/manager-react-components/src/components/content/index.ts b/packages/manager-react-components/src/components/content/index.ts index 43a77ecc2883..f09a622c064f 100644 --- a/packages/manager-react-components/src/components/content/index.ts +++ b/packages/manager-react-components/src/components/content/index.ts @@ -1,4 +1,5 @@ export * from './headers/headers.component'; export * from './price/price.component'; +export * from './price/price.utils'; export * from './dashboard-tile/dashboard-tile.component'; export * from './dashboard-tile/tile-block.component'; diff --git a/packages/manager/apps/ips/package.json b/packages/manager/apps/ips/package.json index 6ce39ee11a1e..36c8f23f8535 100644 --- a/packages/manager/apps/ips/package.json +++ b/packages/manager/apps/ips/package.json @@ -23,13 +23,13 @@ "@ovh-ux/manager-config": "*", "@ovh-ux/manager-core-api": "*", "@ovh-ux/manager-core-utils": "*", + "@ovh-ux/manager-module-order": "0.9.3", "@ovh-ux/manager-react-components": "^2.6.2", "@ovh-ux/manager-react-core-application": "*", "@ovh-ux/manager-react-shell-client": "^0.8.5", - "@ovh-ux/manager-module-order": "0.9.3", "@ovh-ux/request-tagger": "^0.4.0", - "@ovhcloud/ods-components": "18.4.1", - "@ovhcloud/ods-themes": "^18.4.1", + "@ovhcloud/ods-components": "18.5.0", + "@ovhcloud/ods-themes": "^18.5.0", "i18next": "^23.8.2", "i18next-http-backend": "^2.4.2", "jsurl": "0.1.5", diff --git a/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json index 203cb9c295e8..06536e18cdf7 100644 --- a/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json +++ b/packages/manager/apps/ips/public/translations/ips/Messages_fr_FR.json @@ -1,6 +1,4 @@ { - "per_ip": "/IP", - "per_ip_full": "par adresse IP", "breadcrumb_root_label": "Toutes les IPs", "order": "Commander une IP" } diff --git a/packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json index 50f99442e575..311e0eb52747 100644 --- a/packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json +++ b/packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json @@ -1,6 +1,8 @@ { "title": "Commander des Additional IP", "back_link": "Retour à la page précédente", + "per_ip": "/IP", + "per_ip_full": "par adresse IP", "ip_version_title": "Sélectionner la version de l'adresse IP", "ip_version_description": "Pour un certain nombre de cas, vous aurez peut-être besoin d'une IPv4 standard, la plus utilisée, ou d'une nouvelle version du protocole - l'IPv6. Veuillez noter que la liste des produits compatibles peut varier.", "ipv4_card_title": "IPv4", @@ -12,11 +14,15 @@ "service_selection_select_label": "Service", "service_selection_select_placeholder": "Sélectionnez...", "service_selection_select_vrack_option_group_label": "Réseau Privé vRack", + "service_selection_select_ip_parking_option_group_label": "IP Parking", + "service_selection_select_ip_parking_option_label": "IP Parking", "region_selection_title": "Sélectionnez une région pour votre nouvelle Additional IP", "offer_selection_title": "Choisissez votre offre", "additional_ip_block_card_title": "Block Additional IP", "additional_ip_block_card_description": "Bloc d'adresse IPv4. Les tailles de bloc disponibles varient en fonction du service associé.", "additional_ip_card_title": "Additional IP", + "additional_ip_card_ripe_description": "Additional IPv4 RIPE - {{price}}", + "additional_ip_card_arin_description": "Additional IPv4 ARIN - {{price}}", "geolocation_selection_title": "Géolocalisation de l'adresse IP", "geolocation_selection_description": "Veuillez choisir l'un des emplacements disponibles pour votre nouveau bloc d'adresses IPv4 supplémentaire.", "organisation_selection_title": "Associer une organisation", diff --git a/packages/manager/apps/ips/src/components/OfferCard/OfferCard.component.tsx b/packages/manager/apps/ips/src/components/OfferCard/OfferCard.component.tsx deleted file mode 100644 index bd6cced00f50..000000000000 --- a/packages/manager/apps/ips/src/components/OfferCard/OfferCard.component.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React from 'react'; -import { - IntervalUnitType, - OvhSubsidiary, - Price, -} from '@ovh-ux/manager-react-components'; -import { ShellContext } from '@ovh-ux/manager-react-shell-client'; -import { - ODS_CARD_COLOR, - ODS_SPINNER_SIZE, - ODS_TEXT_PRESET, -} from '@ovhcloud/ods-components'; -import { - OdsCard, - OdsDivider, - OdsSpinner, - OdsText, - OdsSelect, - OdsSkeleton, -} from '@ovhcloud/ods-components/react'; -import { useTranslation } from 'react-i18next'; -import './offer-card.scss'; - -export type OfferCardProps = { - className?: string; - isDisabled?: boolean; - isSelected?: boolean; - onClick?: () => void; - setSelectedPlanCode: React.Dispatch>; - selectedPlanCode: string; - title: string; - description: string; - price: number; - options?: { label: string; value: string }[]; - isLoading?: boolean; -}; - -export const OfferCard: React.FC = ({ - className, - isDisabled, - isSelected, - onClick, - selectedPlanCode, - setSelectedPlanCode, - title, - description, - price, - options, - isLoading, -}) => { - const { environment } = React.useContext(ShellContext); - const { t, i18n } = useTranslation(); - const stateStyle = isDisabled - ? 'cursor-not-allowed bg-neutral-100' - : 'cursor-pointer'; - const borderStyle = isSelected - ? 'offer_card_selected' - : 'm-[1px] hover:shadow-md'; - - return ( - !isDisabled && onClick?.()} - color={ODS_CARD_COLOR.neutral} - > - - {title} - - - {price === null ? ( - - ) : ( - - )} - - - {description} - - - {isLoading ? ( - - ) : ( - result + value, '')} - name={title} - value={selectedPlanCode} - onOdsChange={(event) => - setSelectedPlanCode(event.target.value as string) - } - > - {options.map(({ label, value }) => ( - - ))} - - )} - - ); -}; diff --git a/packages/manager/apps/ips/src/components/OfferCard/offer-card.scss b/packages/manager/apps/ips/src/components/OfferCard/offer-card.scss deleted file mode 100644 index 9c2031ecf411..000000000000 --- a/packages/manager/apps/ips/src/components/OfferCard/offer-card.scss +++ /dev/null @@ -1,4 +0,0 @@ -.offer_card_selected { - border: 2px solid #0050d7; - margin: 0; -} diff --git a/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx b/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx index c78fa74a5ac8..bf074f3e767c 100644 --- a/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx +++ b/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx @@ -13,21 +13,22 @@ import { import { OdsCard, OdsDivider, + OdsSkeleton, OdsSpinner, OdsText, } from '@ovhcloud/ods-components/react'; import { useTranslation } from 'react-i18next'; import './option-card.scss'; -export type OptionCardProps = { +export type OptionCardProps = React.PropsWithChildren<{ className?: string; isDisabled?: boolean; isSelected?: boolean; onClick?: () => void; title: React.ReactNode; - description: string; - price: number; -}; + subtitle?: React.ReactNode; + description?: string; +}>; export const OptionCard: React.FC = ({ className, @@ -36,10 +37,9 @@ export const OptionCard: React.FC = ({ onClick, title, description, - price, + subtitle, + children, }) => { - const { environment } = React.useContext(ShellContext); - const { t, i18n } = useTranslation(); const stateStyle = isDisabled ? 'cursor-not-allowed bg-neutral-100' : 'cursor-pointer hover:shadow-md'; @@ -56,26 +56,52 @@ export const OptionCard: React.FC = ({ > {title} - {description} + {subtitle && ( + + {subtitle} + + )} + {description && ( + {description} + )} - - {price === null ? ( - - ) : ( - - )} - + {children} ); }; + +export type PriceFooterProps = { + price: number | null; + suffix?: string; + isStartingPrice?: boolean; +}; + +export const PriceDescription: React.FC = ({ + price, + suffix, + isStartingPrice, +}) => { + const { i18n } = useTranslation(); + const { environment } = React.useContext(ShellContext); + + return ( + + {price === null ? ( + + ) : ( + + )} + + ); +}; diff --git a/packages/manager/apps/ips/src/data/hooks/catalog/catalog.utils.ts b/packages/manager/apps/ips/src/data/hooks/catalog/catalog.utils.ts index f377fab9819c..8433aa2685fa 100644 --- a/packages/manager/apps/ips/src/data/hooks/catalog/catalog.utils.ts +++ b/packages/manager/apps/ips/src/data/hooks/catalog/catalog.utils.ts @@ -1,5 +1,16 @@ +import { + isRegionInEu, + isRegionInUs, +} from '@/components/RegionSelector/region-selector.utils'; import { CatalogIpPlan } from '../../api/catalog'; +export const getContinentKeyFromRegion = (region: string) => { + if (isRegionInEu(region)) { + return 'EU'; + } + return isRegionInUs(region) ? 'US' : 'CA'; +}; + export const IP_FAILOVER_PLANCODE = { EU: 'ip-failover-ripe', CA: 'ip-failover-arin', diff --git a/packages/manager/apps/ips/src/data/hooks/useServiceList.ts b/packages/manager/apps/ips/src/data/hooks/useServiceList.ts new file mode 100644 index 000000000000..288708d49453 --- /dev/null +++ b/packages/manager/apps/ips/src/data/hooks/useServiceList.ts @@ -0,0 +1,66 @@ +import { useQueries } from '@tanstack/react-query'; +import { getVrackList } from '../api/vrack'; + +export enum ServiceType { + vrack = 'vrack', + vps = 'vps', + dedicatedCloud = 'dedicatedCloud', + ipParking = 'ipParking', + server = 'server', + unknown = 'unknown', +} + +export const ipParkingOptionValue = 'parking'; + +/** + * Fetch the list of available services to order an additional IP + */ +export const useServiceList = () => { + const queries = useQueries({ + queries: [ + { + queryKey: ['dedicatedCloud'], + queryFn: () => [], + }, + { + queryKey: ['server'], + queryFn: () => [], + }, + { + queryKey: ['vps'], + queryFn: () => [], + }, + { + queryKey: ['vrack'], + queryFn: getVrackList, + }, + ], + }); + + return { + getServiceType: (serviceId: string) => { + if (serviceId === ipParkingOptionValue) { + return ServiceType.ipParking; + } + if (queries[0]?.data?.includes(serviceId)) { + return ServiceType.dedicatedCloud; + } + if (queries[1]?.data?.includes(serviceId)) { + return ServiceType.server; + } + if (queries[2]?.data?.includes(serviceId)) { + return ServiceType.vps; + } + return queries[3]?.data?.data?.includes(serviceId) + ? ServiceType.vrack + : ServiceType.unknown; + }, + dedicatedCloud: queries[0]?.data, + server: queries[1], + vps: queries[2], + vrack: queries[3]?.data?.data, + isError: queries.some((query) => query.isError), + error: queries.find((query) => query.error), + isLoading: queries.some((query) => query.isLoading), + }; +}; diff --git a/packages/manager/apps/ips/src/pages/order/Ipv4Order.component.tsx b/packages/manager/apps/ips/src/pages/order/Ipv4Order.component.tsx index bcbedce3040c..24e379d05117 100644 --- a/packages/manager/apps/ips/src/pages/order/Ipv4Order.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/Ipv4Order.component.tsx @@ -6,11 +6,13 @@ import { RegionSelectionSection } from './sections/RegionSelectionSection.compon import { OfferSelectionSection } from './sections/OfferSelectionSection.component'; import { GeolocationSection } from './sections/GeolocationSection.component'; import { OrganisationSection } from './sections/OrganisationSection.component'; +import { ServiceType } from '@/data/hooks/useServiceList'; export const Ipv4Order: React.FC = () => { const { ipVersion, selectedService, + selectedServiceType, selectedRegion, selectedOffer, selectedPlanCode, @@ -24,13 +26,17 @@ export const Ipv4Order: React.FC = () => { return ( <> - {!!selectedService && } + {[ServiceType.ipParking, ServiceType.vrack].includes( + selectedServiceType, + ) && + !!selectedService && } {!!selectedService && !!selectedRegion && } {!!selectedService && !!selectedOffer && !!selectedRegion && !!selectedPlanCode && } - {!!selectedService && + {[ServiceType.vrack].includes(selectedServiceType) && + !!selectedService && !!selectedOffer && !!selectedRegion && !!selectedPlanCode && diff --git a/packages/manager/apps/ips/src/pages/order/order.context.tsx b/packages/manager/apps/ips/src/pages/order/order.context.tsx index ed33d05adc0b..3ca80b5b697e 100644 --- a/packages/manager/apps/ips/src/pages/order/order.context.tsx +++ b/packages/manager/apps/ips/src/pages/order/order.context.tsx @@ -1,17 +1,22 @@ import React from 'react'; import { IpOffer, IpVersion } from './order.constant'; +import { ServiceType } from '@/data/hooks/useServiceList'; export type OrderContextType = { ipVersion?: IpVersion; setIpVersion: React.Dispatch>; selectedService?: string; setSelectedService: React.Dispatch>; + selectedServiceType: ServiceType; + setSelectedServiceType: React.Dispatch>; selectedRegion?: string; setSelectedRegion: React.Dispatch>; selectedOffer?: IpOffer; setSelectedOffer: React.Dispatch>; selectedPlanCode?: string; setSelectedPlanCode: React.Dispatch>; + ipQuantity: number; + setIpQuantity: React.Dispatch>; selectedGeolocation?: string; setSelectedGeolocation: React.Dispatch>; selectedOrganisation?: string; @@ -21,9 +26,13 @@ export type OrderContextType = { export const OrderContext = React.createContext({ setIpVersion: () => null, setSelectedService: () => null, + selectedServiceType: ServiceType.unknown, + setSelectedServiceType: () => null, setSelectedRegion: () => null, setSelectedOffer: () => null, setSelectedPlanCode: () => null, + ipQuantity: 1, + setIpQuantity: () => null, setSelectedGeolocation: () => null, setSelectedOrganisation: () => null, }); @@ -33,9 +42,13 @@ export const OrderContextProvider: React.FC = ({ }) => { const [ipVersion, setIpVersion] = React.useState(null); const [selectedService, setSelectedService] = React.useState(null); + const [selectedServiceType, setSelectedServiceType] = React.useState( + ServiceType.unknown, + ); const [selectedRegion, setSelectedRegion] = React.useState(null); const [selectedOffer, setSelectedOffer] = React.useState(null); const [selectedPlanCode, setSelectedPlanCode] = React.useState(null); + const [ipQuantity, setIpQuantity] = React.useState(1); const [selectedGeolocation, setSelectedGeolocation] = React.useState(null); const [selectedOrganisation, setSelectedOrganisation] = React.useState(null); @@ -46,12 +59,16 @@ export const OrderContextProvider: React.FC = ({ setIpVersion, selectedService, setSelectedService, + selectedServiceType, + setSelectedServiceType, selectedRegion, setSelectedRegion, selectedOffer, setSelectedOffer, selectedPlanCode, setSelectedPlanCode, + ipQuantity, + setIpQuantity, selectedGeolocation, setSelectedGeolocation, selectedOrganisation, diff --git a/packages/manager/apps/ips/src/pages/order/order.utils.ts b/packages/manager/apps/ips/src/pages/order/order.utils.ts index 446e92a54576..68cade17b724 100644 --- a/packages/manager/apps/ips/src/pages/order/order.utils.ts +++ b/packages/manager/apps/ips/src/pages/order/order.utils.ts @@ -1,23 +1,37 @@ import JSURL from 'jsurl'; import { IpOffer, IpVersion } from './order.constant'; -import { getDatacenterFromRegion } from '@/data/hooks/catalog/catalog.utils'; +import { + IP_FAILOVER_PLANCODE, + getContinentKeyFromRegion, + getDatacenterFromRegion, +} from '@/data/hooks/catalog/catalog.utils'; +import { ServiceType } from '@/data/hooks/useServiceList'; export type OrderParams = { serviceName: string; + serviceType: ServiceType; planCode: string; ipVersion: IpVersion; - region: string; + region?: string; offer: IpOffer; geolocation: string; organisation: string; quantity?: number; - isPrivateCloud?: boolean; }; +const getPlanCode = ( + planCode: string, + serviceType: ServiceType, + region: string, +) => + serviceType === ServiceType.ipParking + ? IP_FAILOVER_PLANCODE[getContinentKeyFromRegion(region)] + : planCode; + export const getAdditionalIpsProductSettings = ({ serviceName, + serviceType, planCode, - isPrivateCloud = false, region, offer, geolocation, @@ -27,14 +41,28 @@ export const getAdditionalIpsProductSettings = ({ JSURL.stringify({ configuration: [ { label: 'destination', value: serviceName }, - { label: 'country', value: geolocation.toUpperCase() }, - { label: 'organisation', value: organisation }, - { label: 'datacenter', value: getDatacenterFromRegion(region) }, - ], + geolocation && { label: 'country', value: geolocation.toUpperCase() }, + organisation && { label: 'organisation', value: organisation }, + region && { label: 'datacenter', value: getDatacenterFromRegion(region) }, + ].filter(Boolean), duration: 'P1M', - planCode, + planCode: getPlanCode(planCode, serviceType, region), pricingMode: 'default', - productId: isPrivateCloud ? 'privateCloud' : 'ip', + productId: + serviceType === ServiceType.dedicatedCloud ? 'privateCloud' : 'ip', quantity: offer === IpOffer.blockAdditionalIp ? 1 : quantity, - datacenter: getDatacenterFromRegion(region), + datacenter: region ? getDatacenterFromRegion(region) : null, }); + +export const hasAdditionalIpOffer = (serviceType: ServiceType) => + [ServiceType.ipParking, ServiceType.server, ServiceType.vps].includes( + serviceType, + ); + +export const hasAdditionalIpBlockOffer = (serviceType: ServiceType) => + [ + ServiceType.dedicatedCloud, + ServiceType.ipParking, + ServiceType.vrack, + ServiceType.server, + ].includes(serviceType); diff --git a/packages/manager/apps/ips/src/pages/order/sections/GeolocationSection.component.tsx b/packages/manager/apps/ips/src/pages/order/sections/GeolocationSection.component.tsx index f6f29ae382d6..0128bd7d1bcf 100644 --- a/packages/manager/apps/ips/src/pages/order/sections/GeolocationSection.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/sections/GeolocationSection.component.tsx @@ -5,6 +5,7 @@ import { OrderSection } from '../../../components/OrderSection/OrderSection.comp import { useAvailableGeolocationFromPlanCode } from '@/data/hooks/catalog'; import { getCountryCode } from '@/components/RegionSelector/region-selector.utils'; import { OrderContext } from '../order.context'; +import { IpOffer } from '../order.constant'; export const GeolocationSection: React.FC = () => { const { @@ -12,6 +13,7 @@ export const GeolocationSection: React.FC = () => { selectedPlanCode, selectedGeolocation, setSelectedGeolocation, + selectedOffer, } = React.useContext(OrderContext); const { t } = useTranslation('order'); const { t: tRegionSelector } = useTranslation('region-selector'); @@ -35,6 +37,7 @@ export const GeolocationSection: React.FC = () => { key={geolocations.join('-')} className="block w-full max-w-[384px]" name="ip-geolocation" + isDisabled={selectedOffer === IpOffer.additionalIp} onOdsChange={(event) => setSelectedGeolocation(event.target.value as string) } diff --git a/packages/manager/apps/ips/src/pages/order/sections/IpVersionSection.component.tsx b/packages/manager/apps/ips/src/pages/order/sections/IpVersionSection.component.tsx index f7f384786569..e557b6a9c0ce 100644 --- a/packages/manager/apps/ips/src/pages/order/sections/IpVersionSection.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/sections/IpVersionSection.component.tsx @@ -2,7 +2,10 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; import { OdsBadge } from '@ovhcloud/ods-components/react'; import { ODS_BADGE_COLOR, ODS_BADGE_SIZE } from '@ovhcloud/ods-components'; -import { OptionCard } from '@/components/OptionCard/OptionCard.component'; +import { + OptionCard, + PriceDescription, +} from '@/components/OptionCard/OptionCard.component'; import { IpVersion } from '../order.constant'; import { OrderSection } from '../../../components/OrderSection/OrderSection.component'; import { useIpv4LowestPrice } from '@/data/hooks/catalog'; @@ -22,10 +25,15 @@ export const IpVersionSection: React.FC = () => { setIpVersion(IpVersion.ipv4)} - /> + > + + @@ -39,11 +47,12 @@ export const IpVersionSection: React.FC = () => { } description={t('ipv6_card_description')} - price={0} isDisabled isSelected={ipVersion === IpVersion.ipv6} onClick={() => setIpVersion(IpVersion.ipv6)} - /> + > + + ); diff --git a/packages/manager/apps/ips/src/pages/order/sections/OfferSelectionSection.component.tsx b/packages/manager/apps/ips/src/pages/order/sections/OfferSelectionSection.component.tsx index 87a7c1686fb4..f7a359eb84b5 100644 --- a/packages/manager/apps/ips/src/pages/order/sections/OfferSelectionSection.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/sections/OfferSelectionSection.component.tsx @@ -1,25 +1,50 @@ import React from 'react'; import { useTranslation } from 'react-i18next'; -import { OfferCard } from '@/components/OfferCard/OfferCard.component'; -import { IpOffer, IpVersion } from '../order.constant'; -import { OrderSection } from '../../../components/OrderSection/OrderSection.component'; +import { + OvhSubsidiary, + getPrice, + getPriceTextFormatted, +} from '@ovh-ux/manager-react-components'; +import { + OdsSelect, + OdsSkeleton, + OdsText, + OdsQuantity, +} from '@ovhcloud/ods-components/react'; +import { ODS_TEXT_PRESET } from '@ovhcloud/ods-components'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { IpOffer } from '../order.constant'; +import { isRegionInEu } from '@/components/RegionSelector/region-selector.utils'; +import { OrderSection } from '@/components/OrderSection/OrderSection.component'; +import { + OptionCard, + PriceDescription, +} from '@/components/OptionCard/OptionCard.component'; import { useAdditionalIpBlockPricings, useIpv4LowestPrice, } from '@/data/hooks/catalog'; import { OrderContext } from '../order.context'; +import { + hasAdditionalIpBlockOffer, + hasAdditionalIpOffer, +} from '../order.utils'; export const OfferSelectionSection: React.FC = () => { const { ipVersion, + selectedServiceType, selectedRegion, selectedOffer, setSelectedOffer, selectedPlanCode, + ipQuantity, + setIpQuantity, setSelectedPlanCode, } = React.useContext(OrderContext); - const { t } = useTranslation('order'); + const { t, i18n } = useTranslation('order'); const { price } = useIpv4LowestPrice(); + const { environment } = React.useContext(ShellContext); const { pricingList, isLoading } = useAdditionalIpBlockPricings( selectedRegion, ipVersion, @@ -38,17 +63,70 @@ export const OfferSelectionSection: React.FC = () => { return (
- setSelectedOffer(IpOffer.blockAdditionalIp)} - setSelectedPlanCode={setSelectedPlanCode} - selectedPlanCode={selectedPlanCode} - options={pricingList} - isLoading={isLoading} - /> + {hasAdditionalIpOffer(selectedServiceType) && ( + } + description={t( + `additional_ip_card_${ + isRegionInEu(selectedRegion) ? 'ripe' : 'arin' + }_description`, + { + price: getPriceTextFormatted( + environment.user.ovhSubsidiary as OvhSubsidiary, + i18n.language, + getPrice(price, 0), + ), + }, + )} + isSelected={selectedOffer === IpOffer.additionalIp} + onClick={() => setSelectedOffer(IpOffer.additionalIp)} + > + setIpQuantity(event.target.value)} + value={ipQuantity} + /> + + + + + )} + {hasAdditionalIpBlockOffer(selectedServiceType) && ( + + } + isSelected={selectedOffer === IpOffer.blockAdditionalIp} + onClick={() => setSelectedOffer(IpOffer.blockAdditionalIp)} + > + {isLoading ? ( + + ) : ( + result + value, + '', + )} + name="ip_block_plancode_select" + value={selectedPlanCode} + onOdsChange={(event) => + setSelectedPlanCode(event.target.value as string) + } + > + {pricingList.map(({ label, value }) => ( + + ))} + + )} + + )}
); diff --git a/packages/manager/apps/ips/src/pages/order/sections/OrderButtonSection.component.tsx b/packages/manager/apps/ips/src/pages/order/sections/OrderButtonSection.component.tsx index 36833f31f675..36d08bcf320d 100644 --- a/packages/manager/apps/ips/src/pages/order/sections/OrderButtonSection.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/sections/OrderButtonSection.component.tsx @@ -11,16 +11,19 @@ import { useNavigate } from 'react-router-dom'; import { getAdditionalIpsProductSettings } from '../order.utils'; import { OrderContext } from '../order.context'; import { urls } from '@/routes/routes.constant'; +import { ServiceType } from '@/data/hooks/useServiceList'; export const OrderButtonSection: React.FC = () => { const { ipVersion, selectedService, + selectedServiceType, selectedOffer, selectedRegion, selectedPlanCode, selectedGeolocation, selectedOrganisation, + ipQuantity, } = React.useContext(OrderContext); const { t } = useTranslation('order'); const navigate = useNavigate(); @@ -30,11 +33,11 @@ export const OrderButtonSection: React.FC = () => { if ( !ipVersion || !selectedService || + !selectedServiceType || !selectedOffer || - !selectedRegion || !selectedPlanCode || !selectedGeolocation || - !selectedOrganisation + (!selectedOrganisation && [ServiceType.vrack].includes(selectedServiceType)) ) { return <>; } @@ -54,6 +57,8 @@ export const OrderButtonSection: React.FC = () => { planCode: selectedPlanCode, region: selectedRegion, serviceName: selectedService, + serviceType: selectedServiceType, + quantity: ipQuantity, }); window.open( `${orderBaseUrl}?products=~(${settings})`, diff --git a/packages/manager/apps/ips/src/pages/order/sections/ServiceSelectionSection.component.tsx b/packages/manager/apps/ips/src/pages/order/sections/ServiceSelectionSection.component.tsx index 5b000e524e8d..6bb9d805f6a7 100644 --- a/packages/manager/apps/ips/src/pages/order/sections/ServiceSelectionSection.component.tsx +++ b/packages/manager/apps/ips/src/pages/order/sections/ServiceSelectionSection.component.tsx @@ -7,20 +7,21 @@ import { OdsSkeleton, } from '@ovhcloud/ods-components/react'; import { ODS_MESSAGE_COLOR } from '@ovhcloud/ods-components'; -import { useQuery } from '@tanstack/react-query'; -import { OrderSection } from '../../../components/OrderSection/OrderSection.component'; -import { getVrackList } from '@/data/api/vrack'; +import { OrderSection } from '@/components/OrderSection/OrderSection.component'; +import { + ipParkingOptionValue, + useServiceList, +} from '@/data/hooks/useServiceList'; import { OrderContext } from '../order.context'; export const ServiceSelectionSection: React.FC = () => { - const { selectedService, setSelectedService } = React.useContext( - OrderContext, - ); + const { + selectedService, + setSelectedService, + setSelectedServiceType, + } = React.useContext(OrderContext); const { t } = useTranslation('order'); - const { data, isLoading, isError, error } = useQuery({ - queryKey: ['vrack'], - queryFn: getVrackList, - }); + const { vrack, getServiceType, isLoading, isError, error } = useServiceList(); return ( @@ -41,18 +42,29 @@ export const ServiceSelectionSection: React.FC = () => { id="service" name="service" isDisabled={isLoading || isError} - onOdsChange={(event) => - setSelectedService(event.target.value as string) - } + onOdsChange={(event) => { + const serviceId = event.target.value as string; + setSelectedService(serviceId); + setSelectedServiceType(getServiceType(serviceId)); + }} value={selectedService} placeholder={t('service_selection_select_placeholder')} > + + + - {data?.data?.map((vrack) => ( - ))} diff --git a/yarn.lock b/yarn.lock index 154e4f8e3682..5b1634a59ffe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5959,19 +5959,7 @@ "@ovhcloud/ods-common-stencil" "17.2.2" "@ovhcloud/ods-common-theming" "17.2.2" -"@ovhcloud/ods-components@18.4.1", "@ovhcloud/ods-components@^18.3.0": - version "18.4.1" - resolved "https://registry.yarnpkg.com/@ovhcloud/ods-components/-/ods-components-18.4.1.tgz#44e21d23fbf844348e94b966d2aa5c38d8376dc8" - integrity sha512-aS7BArn0691hyHAn2/ND/7XYIX01gAmVCSvEl8uT9umT4iDDvodipkWRtGxBUin6ndvyED1Jx0BfCbtYsrKPeQ== - dependencies: - "@floating-ui/dom" "1.6.11" - "@stencil/core" "4.16.0" - google-libphonenumber "3.2.35" - tom-select "2.3.1" - tslib "2.6.3" - vanillajs-datepicker "1.3.4" - -"@ovhcloud/ods-components@^18.4.1": +"@ovhcloud/ods-components@18.5.0", "@ovhcloud/ods-components@^18.4.1", "@ovhcloud/ods-components@^18.5.0": version "18.5.0" resolved "https://registry.yarnpkg.com/@ovhcloud/ods-components/-/ods-components-18.5.0.tgz#f5b9d3ace2dafab19340e1457a0d113ccb09a52c" integrity sha512-578ljWS0bQOROt2LTFMDVP8NIaSXu1K8OjqzaXnSBdw1sL/zbKCmtsvYMyaAuOTRK1iwYqL7BI45L2IIu9J4ig== @@ -5997,11 +5985,16 @@ dependencies: "@ovhcloud/ods-common-theming" "17.2.2" -"@ovhcloud/ods-themes@^18.3.0", "@ovhcloud/ods-themes@^18.4.1": +"@ovhcloud/ods-themes@^18.4.1": version "18.4.1" resolved "https://registry.yarnpkg.com/@ovhcloud/ods-themes/-/ods-themes-18.4.1.tgz#1c8dfeff1ba0b829fd61e8dea41af4cdbed46912" integrity sha512-bezBp/Bgbo19IFPJ/+a/bFt2IArjq8wGrHPshpk/bVVZsxkgpAiUfRERmftU+l7gYU3e+yvFdopNL6eRaJDPWQ== +"@ovhcloud/ods-themes@^18.5.0": + version "18.5.0" + resolved "https://registry.yarnpkg.com/@ovhcloud/ods-themes/-/ods-themes-18.5.0.tgz#0da59e081ee1db44dadf5c3a63e49f73fc280ea3" + integrity sha512-PAwRtQOYQ1mVVP1RMt2J65NMf7tRbk+jDEf46jUWwRsS2gFuzNrwH3pfgKk4G/nksZ5exTKNL0MeC/fLLVr4Wg== + "@ovhcloud/reket-axios-client@^0.2.1": version "0.2.1" resolved "https://registry.yarnpkg.com/@ovhcloud/reket-axios-client/-/reket-axios-client-0.2.1.tgz#835997e9d3514a5855dc4d7ac867abe8b2c8638d"