From bd661ab6b7770a0a667b7354eac9c3033d3b1007 Mon Sep 17 00:00:00 2001 From: Nicolas Pierre-charles Date: Fri, 3 Jan 2025 16:31:28 +0100 Subject: [PATCH] feat(ips): order ipv4 vrack ref: MANAGER-15838 Signed-off-by: Nicolas Pierre-charles --- .../manager-react-components/package.json | 1 - .../content/price/price.component.tsx | 39 ++++++++- .../components/content/price/price.utils.ts | 2 + .../price/translations/Messages_fr_FR.json | 1 + .../typography/links/links.component.tsx | 5 +- packages/manager/apps/ips/package.json | 4 +- .../translations/ips/Messages_fr_FR.json | 3 +- .../translations/listing/Messages_fr_FR.json | 3 +- .../translations/order/Messages_fr_FR.json | 17 ++++ .../src/components/Breadcrumb/Breadcrumb.tsx | 2 +- .../Breadcrumb}/useBreadcrumb.tsx | 0 .../OptionCard/OptionCard.component.tsx | 85 +++++++++++++++++++ .../manager/apps/ips/src/data/api/catalog.ts | 65 ++++++++++++++ .../manager/apps/ips/src/data/api/vrack.ts | 4 + .../ips/src/data/hooks/catalog.constant.ts | 25 ++++++ .../apps/ips/src/data/hooks/catalog.ts | 66 ++++++++++++++ packages/manager/apps/ips/src/index.tsx | 1 - .../apps/ips/src/pages/listing/index.tsx | 11 ++- .../apps/ips/src/pages/onboarding/index.tsx | 43 +++++++++- .../onboarding}/useGuideUtils.tsx | 0 .../order/IpVersionSection.component.tsx | 60 +++++++++++++ .../apps/ips/src/pages/order/Order.page.tsx | 52 ++++++++++++ .../pages/order/OrderSection.component.tsx | 19 +++++ .../RegionSelectionSection.component.tsx | 48 +++++++++++ .../ServiceSelectionSection.component.tsx | 69 +++++++++++++++ .../ips/src/pages/order/order.constant.ts | 4 + .../apps/ips/src/routes/routes.constant.ts | 1 + .../manager/apps/ips/src/routes/routes.tsx | 11 +++ yarn.lock | 16 +--- 29 files changed, 630 insertions(+), 27 deletions(-) create mode 100644 packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json rename packages/manager/apps/ips/src/{hooks/breadcrumb => components/Breadcrumb}/useBreadcrumb.tsx (100%) create mode 100644 packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx create mode 100644 packages/manager/apps/ips/src/data/api/catalog.ts create mode 100644 packages/manager/apps/ips/src/data/api/vrack.ts create mode 100644 packages/manager/apps/ips/src/data/hooks/catalog.constant.ts create mode 100644 packages/manager/apps/ips/src/data/hooks/catalog.ts rename packages/manager/apps/ips/src/{hooks/guide => pages/onboarding}/useGuideUtils.tsx (100%) create mode 100644 packages/manager/apps/ips/src/pages/order/IpVersionSection.component.tsx create mode 100644 packages/manager/apps/ips/src/pages/order/Order.page.tsx create mode 100644 packages/manager/apps/ips/src/pages/order/OrderSection.component.tsx create mode 100644 packages/manager/apps/ips/src/pages/order/RegionSelectionSection.component.tsx create mode 100644 packages/manager/apps/ips/src/pages/order/ServiceSelectionSection.component.tsx create mode 100644 packages/manager/apps/ips/src/pages/order/order.constant.ts diff --git a/packages/manager-react-components/package.json b/packages/manager-react-components/package.json index 2a3156435474..0ccc5dcdba39 100644 --- a/packages/manager-react-components/package.json +++ b/packages/manager-react-components/package.json @@ -54,7 +54,6 @@ "@ovhcloud/ods-themes": "^18.3.0", "@ovh-ux/manager-core-api": "^0.9.0", "@ovh-ux/manager-react-shell-client": "^0.8.1", - "@ovh-ux/manager-tailwind-config": "^0.2.0", "@ovh-ux/manager-vite-config": "^0.8.1", "@storybook/addon-docs": "^7.5.3", "@storybook/addon-essentials": "7.5.3", diff --git a/packages/manager-react-components/src/components/content/price/price.component.tsx b/packages/manager-react-components/src/components/content/price/price.component.tsx index f75962c90d34..07c7c8da1937 100644 --- a/packages/manager-react-components/src/components/content/price/price.component.tsx +++ b/packages/manager-react-components/src/components/content/price/price.component.tsx @@ -22,6 +22,8 @@ export function Price({ ovhSubsidiary, locale, isConvertIntervalUnit, + isStartingPrice, + suffix = '', }: Readonly) { const { t } = useTranslation('price'); const isAsiaFormat = ['ASIA', 'AU', 'IN', 'SG'].includes(ovhSubsidiary); @@ -81,6 +83,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ({priceWithTax} @@ -105,6 +112,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ), }, @@ -118,6 +130,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ), }, @@ -134,6 +151,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ), }, @@ -150,6 +172,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ({priceWithTax} @@ -171,6 +198,11 @@ export function Price({ {intervalUnitText} + {suffix && ( + + {suffix} + + )} ), }, @@ -181,7 +213,12 @@ export function Price({ return <>; } - return {matchingComponent.component}; + return ( + + {isStartingPrice && value > 0 ? t('price_from_label') : ''} + {matchingComponent.component} + + ); } export default Price; diff --git a/packages/manager-react-components/src/components/content/price/price.utils.ts b/packages/manager-react-components/src/components/content/price/price.utils.ts index e455c2ef2c44..7c0b5470ed74 100644 --- a/packages/manager-react-components/src/components/content/price/price.utils.ts +++ b/packages/manager-react-components/src/components/content/price/price.utils.ts @@ -11,6 +11,8 @@ export interface PriceProps { ovhSubsidiary: OvhSubsidiary; isConvertIntervalUnit?: boolean; locale: string; + suffix?: string; + isStartingPrice?: boolean; } export const getPrice = (value: number, tax?: number): number => { diff --git a/packages/manager-react-components/src/components/content/price/translations/Messages_fr_FR.json b/packages/manager-react-components/src/components/content/price/translations/Messages_fr_FR.json index 9900052fa486..ce894ba22570 100644 --- a/packages/manager-react-components/src/components/content/price/translations/Messages_fr_FR.json +++ b/packages/manager-react-components/src/components/content/price/translations/Messages_fr_FR.json @@ -1,6 +1,7 @@ { "price_ht_label": "HT", "price_ttc_label": "TTC", + "price_from_label": "à partir de ", "price_free": "Inclus", "price_gst_excl_label": "ex. GST", "price_gst_incl_label": "incl. GST", diff --git a/packages/manager-react-components/src/components/typography/links/links.component.tsx b/packages/manager-react-components/src/components/typography/links/links.component.tsx index 2bbdc49fa80f..0f428c9fa0e1 100644 --- a/packages/manager-react-components/src/components/typography/links/links.component.tsx +++ b/packages/manager-react-components/src/components/typography/links/links.component.tsx @@ -46,7 +46,10 @@ export const Links: React.FC = ({ iconAlignment: ODS_LINK_ICON_ALIGNMENT[iconAlignment], })} {...props} - {...(type === LinkType.back && { icon: ODS_ICON_NAME.arrowLeft })} + {...(type === LinkType.back && { + icon: ODS_ICON_NAME.arrowLeft, + iconAlignment: IconLinkAlignmentType.left, + })} {...(type === LinkType.next && { icon: ODS_ICON_NAME.arrowRight })} {...(type === LinkType.external && { icon: ODS_ICON_NAME.externalLink })} label={label} diff --git a/packages/manager/apps/ips/package.json b/packages/manager/apps/ips/package.json index 8941bac22bdd..c9c6a01aff37 100644 --- a/packages/manager/apps/ips/package.json +++ b/packages/manager/apps/ips/package.json @@ -27,8 +27,8 @@ "@ovh-ux/manager-react-core-application": "*", "@ovh-ux/manager-react-shell-client": "^0.8.1", "@ovh-ux/request-tagger": "^0.4.0", - "@ovhcloud/ods-components": "18.4.0", - "@ovhcloud/ods-themes": "^18.4.0", + "@ovhcloud/ods-components": "18.4.1", + "@ovhcloud/ods-themes": "^18.4.1", "flag-icons": "^7.2.3", "i18next": "^23.8.2", "i18next-http-backend": "^2.4.2", 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 c5bf7278da50..664f32085a48 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 @@ -2,5 +2,6 @@ "title": "Bienvenue uapp", "crumb": "ips", "tabs_2": "Tabs 2", - "onboarding": "Onboarding" + "onboarding": "Onboarding", + "per_ip": "/IP" } diff --git a/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json b/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json index c882bc92cfec..15fd5fc56524 100644 --- a/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json +++ b/packages/manager/apps/ips/public/translations/listing/Messages_fr_FR.json @@ -1,4 +1,5 @@ { "title": "Listing page", - "listing_resultats": "résultats" + "listing_resultats": "résultats", + "orderIps": "Commander des IPs" } 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 new file mode 100644 index 000000000000..4b15a428f244 --- /dev/null +++ b/packages/manager/apps/ips/public/translations/order/Messages_fr_FR.json @@ -0,0 +1,17 @@ +{ + "title": "Commander des Additional IP", + "back_link": "Retour à la page précédente", + "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", + "ipv4_card_description": "Standard Internet (à partir d'aujourd'hui). Protocole d'adressage le plus couramment utilisé sur Internet, donc le plus couramment pris en charge.", + "ipv6_card_title": "IPv6", + "ipv6_card_badge_label": "beta", + "ipv6_card_description": "Lorsque de grands blocs IP sont nécessaires et qu'un schéma d'adressage hiérarchique est défini, l'IPv6 est le meilleur choix. Assurez-vous que tous vos clients sont en mesure d'utiliser le nouveau protocole ainsi que vos services", + "service_selection_title": "Sélectionnez le service pour lequel vous désirez acheter des Additional IP", + "service_selection_select_label": "Service", + "service_selection_select_placeholder": "Sélectionnez...", + "service_selection_select_vrack_option_group_label": "Réseau Privé vRack", + "region_selection_title": "Sélectionnez une région pour votre nouvelle Additional IP", + "error_message": "Une erreur est survenue: {{error}}" +} diff --git a/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx b/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx index aebed8e4fee7..a2bbce29995f 100644 --- a/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx +++ b/packages/manager/apps/ips/src/components/Breadcrumb/Breadcrumb.tsx @@ -6,7 +6,7 @@ import { import { useBreadcrumb, BreadcrumbItem, -} from '@/hooks/breadcrumb/useBreadcrumb'; +} from '@/components/Breadcrumb/useBreadcrumb'; import appConfig from '@/ips.config'; export interface BreadcrumbProps { diff --git a/packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx b/packages/manager/apps/ips/src/components/Breadcrumb/useBreadcrumb.tsx similarity index 100% rename from packages/manager/apps/ips/src/hooks/breadcrumb/useBreadcrumb.tsx rename to packages/manager/apps/ips/src/components/Breadcrumb/useBreadcrumb.tsx diff --git a/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx b/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx new file mode 100644 index 000000000000..d4d8d463a62c --- /dev/null +++ b/packages/manager/apps/ips/src/components/OptionCard/OptionCard.component.tsx @@ -0,0 +1,85 @@ +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, +} from '@ovhcloud/ods-components/react'; +import { useTranslation } from 'react-i18next'; + +export const selectedBorderColor = '#0050D7'; + +export type OptionCardProps = { + className?: string; + isDisabled?: boolean; + isSelected?: boolean; + onClick?: () => void; + title: React.ReactNode; + description: string; + price: number; +}; + +export const OptionCard: React.FC = ({ + className, + isDisabled, + isSelected, + onClick, + title, + description, + price, +}) => { + const { environment } = React.useContext(ShellContext); + const { t, i18n } = useTranslation(); + const stateStyle = isDisabled + ? 'cursor-not-allowed bg-neutral-100' + : 'cursor-pointer'; + const color = isSelected ? ODS_CARD_COLOR.primary : ODS_CARD_COLOR.neutral; + const borderStyle = isSelected + ? `border-[2px] border-[${selectedBorderColor}]` + : 'm-[1px]'; + return ( + !isDisabled && onClick?.()} + color={color} + > + + {title} + + {description} + + + {price === null ? ( + + ) : ( + + )} + + + ); +}; diff --git a/packages/manager/apps/ips/src/data/api/catalog.ts b/packages/manager/apps/ips/src/data/api/catalog.ts new file mode 100644 index 000000000000..09e7dac4f094 --- /dev/null +++ b/packages/manager/apps/ips/src/data/api/catalog.ts @@ -0,0 +1,65 @@ +import { ApiResponse, apiClient } from '@ovh-ux/manager-core-api'; +import { CurrencyCode, OvhSubsidiary } from '@ovh-ux/manager-react-components'; + +export type CatalogIpPlan = { + addonsFamily: unknown[]; + consumptionBillingStrategy: string | null; + details: { + metadatas: { key: string; value: 'true' | 'false' }[]; + pricings: { + default: [ + { + capacities: ('installation' | 'renew')[]; + commitment: number; + description: string; + interval: number; + intervalUnit: string; + maximumQuantity: number; + maximumRepeat: number; + minimumQuantity: number; + minimumRepeat: number; + mustBeCompleted: boolean; + price: { currencyCode: CurrencyCode; text: string; value: number }; + priceCapInUcents: number | null; + priceInUcents: number; + pricingStrategy: string; + }, + ]; + }; + product: { + configurations: { + defaultValue: string | null; + isCustom: boolean; + isMandatory: boolean; + name: string; + values: string[]; + }[]; + description: string; + internalType: + | 'cloud_service' + | 'delivery' + | 'deposit' + | 'domain' + | 'implementation_services' + | 'saas_license' + | 'shipping' + | 'storage'; + name: string; + }; + }; + familyName?: string | null; + invoiceName: string; + planCode: string; + pricingType: 'rental' | 'consumption'; +}; + +export type CatalogIpsResponse = { + catalogId: number; + merchantCode: OvhSubsidiary; + plans: CatalogIpPlan[]; +}; + +export const getCatalogIps = ( + sub: OvhSubsidiary = OvhSubsidiary.FR, +): Promise> => + apiClient.v6.get(`/order/catalog/formatted/ip?ovhSubsidiary=${sub}`); diff --git a/packages/manager/apps/ips/src/data/api/vrack.ts b/packages/manager/apps/ips/src/data/api/vrack.ts new file mode 100644 index 000000000000..ee44cb6f550c --- /dev/null +++ b/packages/manager/apps/ips/src/data/api/vrack.ts @@ -0,0 +1,4 @@ +import { ApiResponse, apiClient } from '@ovh-ux/manager-core-api'; + +export const getVrackList = (): Promise> => + apiClient.v6.get('/vrack'); diff --git a/packages/manager/apps/ips/src/data/hooks/catalog.constant.ts b/packages/manager/apps/ips/src/data/hooks/catalog.constant.ts new file mode 100644 index 000000000000..45cc8fd28906 --- /dev/null +++ b/packages/manager/apps/ips/src/data/hooks/catalog.constant.ts @@ -0,0 +1,25 @@ +export const IP_FAILOVER_PLANCODE = { + EU: 'ip-failover-ripe', + CA: 'ip-failover-arin', + US: 'ip-failover-arin', +}; + +export const DATACENTER_TO_REGION: { [datacenter: string]: string } = { + RBX: 'eu-west-rbx', + GRA: 'eu-west-gra', + SBG: 'eu-west-sbg', + PAR: 'eu-west-par', + CR2: 'labeu-west-1-preprod', + LIM: 'eu-west-lim', + WAW: 'eu-central-waw', + ERI: 'eu-west-eri', + BHS: 'ca-east-bhs', + YYZ: 'ca-east-tor', + SGP: 'ap-southeast-sgp', + SYD: 'ap-southeast-syd', + YNM: 'ap-south-mum', + VIN: 'us-east-vin', + HIL: 'us-west-hil', +}; + +export const getCatalogIpsQueryKey = (sub: string) => ['getCatalogIps', sub]; diff --git a/packages/manager/apps/ips/src/data/hooks/catalog.ts b/packages/manager/apps/ips/src/data/hooks/catalog.ts new file mode 100644 index 000000000000..914586eea931 --- /dev/null +++ b/packages/manager/apps/ips/src/data/hooks/catalog.ts @@ -0,0 +1,66 @@ +import { useQuery } from '@tanstack/react-query'; +import React from 'react'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; +import { OvhSubsidiary } from '@ovh-ux/manager-react-components'; +import { getCatalogIps } from '../api/catalog'; +import { + DATACENTER_TO_REGION, + IP_FAILOVER_PLANCODE, + getCatalogIpsQueryKey, +} from './catalog.constant'; + +export const useIpv4LowestPrice = () => { + const { environment } = React.useContext(ShellContext); + const { data, isLoading, isError, error } = useQuery({ + queryKey: getCatalogIpsQueryKey(environment.user.ovhSubsidiary), + queryFn: () => + getCatalogIps(environment.user.ovhSubsidiary as OvhSubsidiary), + }); + + return { + isLoading, + isError, + error, + price: + data?.data?.plans + .filter((plan) => + Object.values(IP_FAILOVER_PLANCODE).includes(plan.planCode), + ) + .reduce((lowestPrice, plan) => { + const currentPrice = plan.details.pricings.default[0].priceInUcents; + return currentPrice < lowestPrice ? currentPrice : lowestPrice; + }, Number.POSITIVE_INFINITY) || null, + }; +}; + +export const useAdditionalIpsRegions = () => { + const { environment } = React.useContext(ShellContext); + const { data, isLoading, isError, error } = useQuery({ + queryKey: getCatalogIpsQueryKey(environment.user.ovhSubsidiary), + queryFn: () => + getCatalogIps(environment.user.ovhSubsidiary as OvhSubsidiary), + }); + + console.log(); + return { + isLoading, + isError, + error, + regionList: data?.data?.plans + ? Array.from( + new Set( + data.data.plans + .map((plan) => + plan.details.product.configurations + .flatMap((config) => + config.name === 'datacenter' ? config : undefined, + ) + .filter(Boolean) + .flatMap((config) => config.values), + ) + .flat(), + ), + ).map((datacenter) => DATACENTER_TO_REGION[datacenter]) + : [], + }; +}; diff --git a/packages/manager/apps/ips/src/index.tsx b/packages/manager/apps/ips/src/index.tsx index 962033765923..74f5e6853fca 100644 --- a/packages/manager/apps/ips/src/index.tsx +++ b/packages/manager/apps/ips/src/index.tsx @@ -6,7 +6,6 @@ import { initI18n, } from '@ovh-ux/manager-react-shell-client'; import App from './App'; -import '@ovhcloud/ods-themes/default'; import './index.scss'; import './vite-hmr'; diff --git a/packages/manager/apps/ips/src/pages/listing/index.tsx b/packages/manager/apps/ips/src/pages/listing/index.tsx index 9f1fee6f8963..f1ec92212c37 100644 --- a/packages/manager/apps/ips/src/pages/listing/index.tsx +++ b/packages/manager/apps/ips/src/pages/listing/index.tsx @@ -10,6 +10,8 @@ import { BaseLayout, } from '@ovh-ux/manager-react-components'; +import { OdsButton } from '@ovhcloud/ods-components/react'; +import { ODS_BUTTON_VARIANT, ODS_ICON_NAME } from '@ovhcloud/ods-components'; import Loading from '@/components/Loading/Loading'; import ErrorBanner from '@/components/Error/Error'; import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; @@ -83,7 +85,14 @@ export default function Listing() { return ( } header={header}> - + navigate(urls.order)} + label={t('orderIps')} + /> + }> {columns && flattenData && ( - + >; +}; + +export const IpVersionSection: React.FC = ({ + ipVersion, + setIpVersion, +}) => { + const { t } = useTranslation('order'); + const { price } = useIpv4LowestPrice(); + + return ( + +
+ setIpVersion(IpVersion.ipv4)} + /> + + {t('ipv6_card_title')} + + + } + 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/Order.page.tsx b/packages/manager/apps/ips/src/pages/order/Order.page.tsx new file mode 100644 index 000000000000..a805fba63255 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/order/Order.page.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { BaseLayout } from '@ovh-ux/manager-react-components'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { OdsBadge, OdsDivider, OdsText } from '@ovhcloud/ods-components/react'; +import { + ODS_BADGE_COLOR, + ODS_BADGE_SIZE, + ODS_TEXT_PRESET, +} from '@ovhcloud/ods-components'; +import { OptionCard } from '@/components/OptionCard/OptionCard.component'; +import { urls } from '@/routes/routes.constant'; +import Breadcrumb from '@/components/Breadcrumb/Breadcrumb'; +import { IpVersionSection } from './IpVersionSection.component'; +import { ServiceSelectionSection } from './ServiceSelectionSection.component'; +import { RegionSelectionSection } from './RegionSelectionSection.component'; +import { IpVersion } from './order.constant'; + +export const OrderPage: React.FC = () => { + const { t } = useTranslation('order'); + const navigate = useNavigate(); + const [ipVersion, setIpVersion] = React.useState(null); + const [selectedService, setSelectedService] = React.useState(null); + const [selectedRegion, setSelectedRegion] = React.useState(null); + + return ( + navigate(urls.listing)} + header={{ + title: t('title'), + }} + breadcrumb={} + > + + {ipVersion === IpVersion.ipv4 && ( + + )} + {ipVersion === IpVersion.ipv4 && !!selectedService && ( + + )} + + ); +}; + +export default OrderPage; diff --git a/packages/manager/apps/ips/src/pages/order/OrderSection.component.tsx b/packages/manager/apps/ips/src/pages/order/OrderSection.component.tsx new file mode 100644 index 000000000000..54793f0806c4 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/order/OrderSection.component.tsx @@ -0,0 +1,19 @@ +import { ODS_TEXT_PRESET } from '@ovhcloud/ods-components'; +import { OdsDivider, OdsText } from '@ovhcloud/ods-components/react'; +import React from 'react'; + +export const OrderSection: React.FC> = ({ title, description, children }) => ( +
+ + {title} + + + {description} + + {children} + +
+); diff --git a/packages/manager/apps/ips/src/pages/order/RegionSelectionSection.component.tsx b/packages/manager/apps/ips/src/pages/order/RegionSelectionSection.component.tsx new file mode 100644 index 000000000000..2820b32d2314 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/order/RegionSelectionSection.component.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { + OdsFormField, + OdsMessage, + OdsSelect, + OdsSkeleton, + OdsSpinner, + OdsText, +} from '@ovhcloud/ods-components/react'; +import { ODS_MESSAGE_COLOR, ODS_TEXT_PRESET } from '@ovhcloud/ods-components'; +import { useQuery } from '@tanstack/react-query'; +import { OrderSection } from './OrderSection.component'; +import { getVrackList } from '@/data/api/vrack'; +import { useAdditionalIpsRegions } from '@/data/hooks/catalog'; +import RegionSelector from '@/components/RegionSelector/RegionSelector'; + +export type RegionSelectionSectionProps = { + selectedRegion?: string; + setSelectedRegion: React.Dispatch>; +}; + +export const RegionSelectionSection: React.FC = ({ + selectedRegion, + setSelectedRegion, +}) => { + const { t } = useTranslation('order'); + const { regionList, isLoading, isError, error } = useAdditionalIpsRegions(); + + return ( + + {isError && ( + + {t('error_message', { error })} + + )} + {isLoading ? ( + + ) : ( + + )} + + ); +}; diff --git a/packages/manager/apps/ips/src/pages/order/ServiceSelectionSection.component.tsx b/packages/manager/apps/ips/src/pages/order/ServiceSelectionSection.component.tsx new file mode 100644 index 000000000000..47a00492e5ee --- /dev/null +++ b/packages/manager/apps/ips/src/pages/order/ServiceSelectionSection.component.tsx @@ -0,0 +1,69 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { + OdsFormField, + OdsMessage, + OdsSelect, + OdsSkeleton, + OdsText, +} from '@ovhcloud/ods-components/react'; +import { ODS_MESSAGE_COLOR, ODS_TEXT_PRESET } from '@ovhcloud/ods-components'; +import { useQuery } from '@tanstack/react-query'; +import { OrderSection } from './OrderSection.component'; +import { getVrackList } from '@/data/api/vrack'; + +export type ServiceSelectionSectionProps = { + selectedService?: string; + setSelectedService: React.Dispatch>; +}; + +export const ServiceSelectionSection: React.FC = ({ + selectedService, + setSelectedService, +}) => { + const { t } = useTranslation('order'); + const { data, isLoading, isError, error } = useQuery({ + queryKey: ['vrack'], + queryFn: getVrackList, + }); + + return ( + + {isError && ( + + {t('error_message', { error })} + + )} + + + {isLoading ? ( + + ) : ( + + setSelectedService(event.target.value as string) + } + value={selectedService} + placeholder={t('service_selection_select_placeholder')} + > + + {data?.data?.map((vrack) => ( + + ))} + + + )} + + + ); +}; diff --git a/packages/manager/apps/ips/src/pages/order/order.constant.ts b/packages/manager/apps/ips/src/pages/order/order.constant.ts new file mode 100644 index 000000000000..1c5bc6cf5347 --- /dev/null +++ b/packages/manager/apps/ips/src/pages/order/order.constant.ts @@ -0,0 +1,4 @@ +export enum IpVersion { + ipv4 = 'ipv4', + ipv6 = 'ipv6', +} diff --git a/packages/manager/apps/ips/src/routes/routes.constant.ts b/packages/manager/apps/ips/src/routes/routes.constant.ts index 8e4539af7f88..52402e25d038 100644 --- a/packages/manager/apps/ips/src/routes/routes.constant.ts +++ b/packages/manager/apps/ips/src/routes/routes.constant.ts @@ -8,4 +8,5 @@ export const urls = { root: subRoutes.root, onboarding: `${subRoutes.root}/${subRoutes.onboarding}`, listing: `${subRoutes.root}`, + order: `/${subRoutes.order}`, }; diff --git a/packages/manager/apps/ips/src/routes/routes.tsx b/packages/manager/apps/ips/src/routes/routes.tsx index 3c538fa5cfd2..37da4b9ee75f 100644 --- a/packages/manager/apps/ips/src/routes/routes.tsx +++ b/packages/manager/apps/ips/src/routes/routes.tsx @@ -43,6 +43,17 @@ export const Routes: any = [ }, }, }, + { + id: 'order', + path: urls.order, + ...lazyRouteConfig(() => import('@/pages/order/Order.page')), + handle: { + tracking: { + pageName: 'order', + pageType: PageType.funnel, + }, + }, + }, ], }, { diff --git a/yarn.lock b/yarn.lock index fa6bd8f486f3..9665eb7a5815 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5253,19 +5253,7 @@ "@ovhcloud/ods-common-stencil" "17.2.2" "@ovhcloud/ods-common-theming" "17.2.2" -"@ovhcloud/ods-components@18.4.0": - version "18.4.0" - resolved "https://registry.yarnpkg.com/@ovhcloud/ods-components/-/ods-components-18.4.0.tgz#039490f269b0aa6309037739a55df129d3bad55d" - integrity sha512-XTEzEInatQIiEavVp556J9bgDlnnrkJLejIzPTQsT1wPfinSA9C8wk0L3bsTIIpSBPOkms6stQT4jLGxW8dH5A== - 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.3.0": +"@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== @@ -5291,7 +5279,7 @@ dependencies: "@ovhcloud/ods-common-theming" "17.2.2" -"@ovhcloud/ods-themes@^18.3.0", "@ovhcloud/ods-themes@^18.4.0": +"@ovhcloud/ods-themes@^18.3.0", "@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==