+ items={allowedRegions}
+ onInput={(reg) =>
+ onSelectRegion(
+ allowedRegions.find(({ name }) => name === reg?.name),
+ )
+ }
+ value={allowedRegions.find(({ name }) => name === region?.name)}
+ columnsCount={3}
+ item={{
+ getId: (item) => item.name,
+ LabelComponent: ({ item, isItemSelected }) => (
+
+ ),
+ }}
+ stack={{
+ by: (item) => item?.macroName || '',
+ LabelComponent: ({ stackKey, isStackSelected }) => (
+
+ ),
+ }}
+ group={{
+ by: (item) => item.continentName,
+ LabelComponent: ({ groupName, isMobile, isGroupSelected }) => (
+
+
+ {groupName ||
+ t('regions:pci_project_regions_list_continent_all')}
+
+
+ ),
+ }}
+ isMobile={false}
+ />
+ )}
+ {region && !region.enabled && (
+
+ {t(
+ 'pci_projects_project_storages_containers_add_add_region_activate',
+ )}
+
+ )}
+ {!isPending && isSubmitted && (
+
+ -
+
+ name === region?.name)
+ ?.microName
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/ContainerTypeStep.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/ContainerTypeStep.component.tsx
new file mode 100644
index 000000000000..4b4e08f86005
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/ContainerTypeStep.component.tsx
@@ -0,0 +1,32 @@
+import { useTranslation } from 'react-i18next';
+import { StepComponent } from '@ovh-ux/manager-react-components';
+import { useContainerCreationStore } from '../useContainerCreationStore';
+
+export function ContainerType() {
+ const { t } = useTranslation(['containers/add', 'pci-common']);
+ const {
+ stepper,
+ submitContainerType,
+ editContainerType,
+ } = useContainerCreationStore();
+ return (
+
+ TODO container type
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/EncryptionStep.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/EncryptionStep.component.tsx
new file mode 100644
index 000000000000..63b80b9db2a1
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/EncryptionStep.component.tsx
@@ -0,0 +1,34 @@
+import { useTranslation } from 'react-i18next';
+import { StepComponent } from '@ovh-ux/manager-react-components';
+import { useContainerCreationStore } from '../useContainerCreationStore';
+
+export function EncryptionStep() {
+ const { t } = useTranslation(['containers/data-encryption', 'pci-common']);
+ const {
+ stepper,
+ submitEncryption,
+ editEncryption,
+ } = useContainerCreationStore();
+ return (
+
+ TODO encryption
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/LinkUserStep.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/LinkUserStep.component.tsx
new file mode 100644
index 000000000000..54c43a3d2592
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/LinkUserStep.component.tsx
@@ -0,0 +1,30 @@
+import { useTranslation } from 'react-i18next';
+import { StepComponent } from '@ovh-ux/manager-react-components';
+import { useContainerCreationStore } from '../useContainerCreationStore';
+
+export function LinkUserStep() {
+ const { t } = useTranslation(['containers/add', 'pci-common']);
+ const { stepper, editUser, submitUser } = useContainerCreationStore();
+ return (
+
+ TODO container name
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionLabel.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionLabel.component.tsx
new file mode 100644
index 000000000000..0843166c6c2e
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionLabel.component.tsx
@@ -0,0 +1,22 @@
+import { ODS_TEXT_LEVEL, ODS_TEXT_SIZE } from '@ovhcloud/ods-components';
+import { OsdsText } from '@ovhcloud/ods-components/react';
+import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
+
+interface RegionLabelProps {
+ label: string;
+ isSelected?: boolean;
+}
+
+export function RegionLabel({ label, isSelected }: Readonly) {
+ return (
+
+
+ {label}
+
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionStep.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionStep.component.tsx
new file mode 100644
index 000000000000..d6188a4244fe
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/RegionStep.component.tsx
@@ -0,0 +1,124 @@
+import { useContext, useEffect, useState } from 'react';
+import { usePrevious } from 'react-use';
+import { ShellContext } from '@ovh-ux/manager-react-shell-client';
+import {
+ NotificationType,
+ StepComponent,
+} from '@ovh-ux/manager-react-components';
+import { useTranslation } from 'react-i18next';
+import { OsdsMessage, OsdsSpinner } from '@ovhcloud/ods-components/react';
+import {
+ useAddProjectRegion,
+ useRefreshProductAvailability,
+} from '@ovh-ux/manager-pci-common';
+import { useParams } from 'react-router-dom';
+import { ODS_MESSAGE_TYPE } from '@ovhcloud/ods-components';
+import { ODS_THEME_COLOR_INTENT } from '@ovhcloud/ods-common-theming';
+import { ApiError } from '@ovh-ux/manager-core-api';
+
+import { useContainerCreationStore } from '../useContainerCreationStore';
+import { OBJECT_CONTAINER_OFFER_SWIFT } from '@/constants';
+import { ContainerRegionSelector } from './ContainerRegionSelector.component';
+
+export function RegionStep() {
+ const { t } = useTranslation(['containers/add', 'pci-common', 'regions']);
+ const context = useContext(ShellContext);
+ const { ovhSubsidiary } = context.environment.getUser();
+ const {
+ form,
+ stepper,
+ setRegion,
+ editRegion,
+ submitRegion,
+ } = useContainerCreationStore();
+ const [addRegionMessage, setAddRegionMessage] = useState();
+ const { projectId } = useParams();
+ const { refresh } = useRefreshProductAvailability(projectId, ovhSubsidiary);
+ const { addRegion, isPending } = useAddProjectRegion({
+ projectId,
+ onSuccess: () => {
+ refresh();
+ setRegion({
+ ...form.region,
+ enabled: true,
+ });
+ submitRegion();
+ setAddRegionMessage(
+
+ {t(
+ 'pci_projects_project_storages_containers_add_add_region_success',
+ {
+ code: form.region.name,
+ },
+ )}
+ ,
+ );
+ },
+ onError: (error: ApiError) => {
+ setAddRegionMessage(
+
+ {t('pci_projects_project_storages_containers_add_add_region_error', {
+ message: error?.response?.data?.message || error?.message || null,
+ requestId: error?.config?.headers['X-OVH-MANAGER-REQUEST-ID'],
+ })}
+ ,
+ );
+ },
+ });
+
+ const submitRegionHandler = () => {
+ if (!form.region.enabled) {
+ addRegion(form.region.name);
+ } else {
+ submitRegion();
+ }
+ };
+
+ const wasLocked = usePrevious(stepper.region.isLocked);
+ useEffect(() => {
+ const { isLocked } = stepper.region;
+ if (wasLocked === true && isLocked === false) {
+ setAddRegionMessage(undefined);
+ }
+ }, [stepper.region.isLocked]);
+
+ return (
+
+ <>
+ {isPending && }
+ {!isPending && (
+
+ )}
+ {addRegionMessage}
+ >
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/VersioningStep.component.tsx b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/VersioningStep.component.tsx
new file mode 100644
index 000000000000..ea92aa292275
--- /dev/null
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/step/VersioningStep.component.tsx
@@ -0,0 +1,34 @@
+import { useTranslation } from 'react-i18next';
+import { StepComponent } from '@ovh-ux/manager-react-components';
+import { useContainerCreationStore } from '../useContainerCreationStore';
+
+export function VersioningStep() {
+ const { t } = useTranslation(['containers/bucket-versioning', 'pci-common']);
+ const {
+ stepper,
+ submitVersioning,
+ editVersioning,
+ } = useContainerCreationStore();
+ return (
+
+ TODO versioning
+
+ );
+}
diff --git a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/useContainerCreationStore.ts b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/useContainerCreationStore.ts
index 6f019acdbe63..3df6772ec40a 100644
--- a/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/useContainerCreationStore.ts
+++ b/packages/manager/apps/pci-object-storage/src/pages/objects/container/new/useContainerCreationStore.ts
@@ -1,9 +1,20 @@
import { create } from 'zustand';
-import { OBJECT_CONTAINER_OFFER_STORAGE_STANDARD } from '@/constants';
+import { TRegionAvailability } from '@ovh-ux/manager-pci-common';
+import {
+ OBJECT_CONTAINER_MODE_LOCAL_ZONE,
+ OBJECT_CONTAINER_OFFER_STORAGE_STANDARD,
+ OBJECT_CONTAINER_OFFER_SWIFT,
+} from '@/constants';
export interface ContainerCreationForn {
offer: string;
deploymentMode: string;
+ region: TRegionAvailability;
+ user: string;
+ versioning: boolean;
+ encryption: boolean;
+ containerName: string;
+ containerType: string;
}
export interface StepState {
@@ -17,99 +28,277 @@ export interface ContainerStore {
stepper: {
offer: StepState;
deployment: StepState;
+ region: StepState;
+ user: StepState;
+ versioning: StepState;
+ encryption: StepState;
+ containerName: StepState;
+ containerType: StepState;
};
+
setOffer: (offer: string) => void;
- setDeploymentMode: (mode: string) => void;
editOffer: () => void;
submitOffer: () => void;
+
+ setDeploymentMode: (mode: string) => void;
editDeploymentMode: () => void;
submitDeploymentMode: () => void;
-}
-export const useContainerCreationStore = create()((set) => {
- /** Unlock and open the 'toEdit' step, closes the other 'toClose' steps and reset their form values */
- const editStep = (toEdit: string, toClose: string[]) => {
- set((state) => ({
- stepper: {
- ...state.stepper,
- [toEdit]: {
- isOpen: true,
- isLocked: false,
- isChecked: false,
- },
- ...toClose.reduce(
- (acc, key) => ({
- ...acc,
- [key]: {
- isOpen: false,
- isLocked: false,
- isChecked: false,
- },
- }),
- {},
- ),
- },
- form: {
- ...state.form,
- ...toClose.reduce(
- (acc, key) => ({
- ...acc,
- [key]: undefined,
- }),
- {},
- ),
- },
- }));
- };
+ setRegion: (region: TRegionAvailability) => void;
+ editRegion: () => void;
+ submitRegion: () => void;
- /** Submit and close the 'toSubmit' step, open and unlock to next step */
- const submitStep = (toSubmit: string, nextState: string) => {
- set((state) => ({
- stepper: {
- ...state.stepper,
- [toSubmit]: {
- isOpen: false,
- isChecked: true,
- isLocked: true,
- },
- [nextState]: {
- isOpen: true,
- isChecked: false,
- isLocked: false,
- },
- },
- }));
- };
+ setUser: (user: string) => void;
+ editUser: () => void;
+ submitUser: () => void;
+
+ setVersioning: (versioning: boolean) => void;
+ editVersioning: () => void;
+ submitVersioning: () => void;
+
+ setEncryption: (encryption: boolean) => void;
+ editEncryption: () => void;
+ submitEncryption: () => void;
+
+ setContainerName: (name: string) => void;
+ editContainerName: () => void;
+ submitContainerName: () => void;
+
+ setContainerType: (type: string) => void;
+ editContainerType: () => void;
+ submitContainerType: () => void;
+}
- return {
- // initial form state
- form: {
- offer: OBJECT_CONTAINER_OFFER_STORAGE_STANDARD,
- deploymentMode: undefined,
- },
- // initial stepper state
- stepper: {
- offer: { isOpen: true },
- deployment: {},
- },
- setOffer: (offer: string) =>
+export const useContainerCreationStore = create()(
+ (set, get) => {
+ /** Unlock and open the 'toEdit' step, closes the other 'toClose' steps and reset their form values */
+ const editStep = (toEdit: string, toClose: string[]) => {
set((state) => ({
+ stepper: {
+ ...state.stepper,
+ [toEdit]: {
+ isOpen: true,
+ isLocked: false,
+ isChecked: false,
+ },
+ ...toClose.reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: {
+ isOpen: false,
+ isLocked: false,
+ isChecked: false,
+ },
+ }),
+ {},
+ ),
+ },
form: {
...state.form,
- offer,
- deploymentMode: undefined,
+ ...toClose.reduce(
+ (acc, key) => ({
+ ...acc,
+ [key]: undefined,
+ }),
+ {},
+ ),
},
- })),
- submitOffer: () => submitStep('offer', 'deployment'),
- editOffer: () => editStep('offer', ['deployment']),
- setDeploymentMode: (deploymentMode: string) =>
+ }));
+ };
+
+ /** Submit and close the 'toSubmit' step, open and unlock to next step */
+ const submitStep = (toSubmit: string, nextState: string) => {
set((state) => ({
- form: {
- ...state.form,
- deploymentMode,
+ stepper: {
+ ...state.stepper,
+ [toSubmit]: {
+ isOpen: false,
+ isChecked: true,
+ isLocked: true,
+ },
+ [nextState]: {
+ isOpen: true,
+ isChecked: false,
+ isLocked: false,
+ },
},
- })),
- submitDeploymentMode: () => submitStep('deployment', 'region'),
- editDeploymentMode: () => editStep('deployment', []),
- };
-});
+ }));
+ };
+
+ return {
+ // initial form state
+ form: {
+ offer: OBJECT_CONTAINER_OFFER_STORAGE_STANDARD,
+ deploymentMode: undefined,
+ region: undefined,
+ user: undefined,
+ versioning: false,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+
+ // initial stepper state
+ stepper: {
+ offer: { isOpen: true },
+ deployment: {},
+ region: {},
+ user: {},
+ versioning: {},
+ encryption: {},
+ containerName: {},
+ containerType: {},
+ },
+
+ setOffer: (offer: string) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ offer,
+ deploymentMode: undefined,
+ region: undefined,
+ user: undefined,
+ versioning: false,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ submitOffer: () =>
+ submitStep(
+ 'offer',
+ get().form.offer === OBJECT_CONTAINER_OFFER_SWIFT
+ ? 'region'
+ : 'deployment',
+ ),
+ editOffer: () =>
+ editStep('offer', [
+ 'deployment',
+ 'region',
+ 'user',
+ 'versioning',
+ 'encryption',
+ 'containerName',
+ ]),
+
+ setDeploymentMode: (deploymentMode: string) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ deploymentMode,
+ region: undefined,
+ user: undefined,
+ versioning: false,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ submitDeploymentMode: () => submitStep('deployment', 'region'),
+ editDeploymentMode: () =>
+ editStep('deployment', [
+ 'region',
+ 'user',
+ 'versioning',
+ 'encryption',
+ 'containerName',
+ ]),
+
+ setRegion: (region: TRegionAvailability) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ region,
+ user: undefined,
+ versioning: false,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ submitRegion: () => {
+ const { form } = get();
+ if (form.offer === OBJECT_CONTAINER_OFFER_SWIFT) {
+ submitStep('region', 'containerType');
+ } else if (form.deploymentMode === OBJECT_CONTAINER_MODE_LOCAL_ZONE) {
+ submitStep('region', 'containerName');
+ } else {
+ submitStep('region', 'user');
+ }
+ },
+ editRegion: () =>
+ editStep('region', [
+ 'user',
+ 'versioning',
+ 'encryption',
+ 'containerName',
+ 'containerType',
+ ]),
+
+ setUser: (user: string) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ user,
+ versioning: false,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ editUser: () =>
+ editStep('user', ['versioning', 'encryption', 'containerName']),
+ submitUser: () => submitStep('user', 'versioning'),
+
+ setVersioning: (versioning: boolean) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ versioning,
+ encryption: false,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ editVersioning: () =>
+ editStep('versioning', ['encryption', 'containerName']),
+ submitVersioning: () => submitStep('versioning', 'encryption'),
+
+ setEncryption: (encryption: boolean) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ encryption,
+ containerName: '',
+ containerType: undefined,
+ },
+ })),
+ editEncryption: () => editStep('encryption', ['containerName']),
+ submitEncryption: () => submitStep('encryption', 'containerName'),
+
+ setContainerName: (containerName: string) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ containerName,
+ containerType: undefined,
+ },
+ })),
+ editContainerName: () => editStep('containerName', []),
+ submitContainerName: () => {
+ // @TODO create container
+ },
+
+ setContainerType: (containerType: string) =>
+ set((state) => ({
+ form: {
+ ...state.form,
+ containerName: '',
+ containerType,
+ },
+ })),
+ editContainerType: () => editStep('containerType', ['containerName']),
+ submitContainerType: () => submitStep('containerType', 'containerName'),
+ };
+ },
+);
diff --git a/packages/manager/modules/manager-pci-common/src/api/data/availability.ts b/packages/manager/modules/manager-pci-common/src/api/data/availability.ts
index 8f4abf8bd7c6..a4393923aaf1 100644
--- a/packages/manager/modules/manager-pci-common/src/api/data/availability.ts
+++ b/packages/manager/modules/manager-pci-common/src/api/data/availability.ts
@@ -1,6 +1,6 @@
import { v6 } from '@ovh-ux/manager-core-api';
-type TRegion = {
+export type TRegionAvailability = {
continentCode: 'ASIA' | 'EU' | 'NA' | 'US';
datacenter: string;
enabled: boolean;
@@ -11,11 +11,11 @@ type TRegion = {
export type TProductAvailability = {
plans: {
code: string;
- regions: TRegion[];
+ regions: TRegionAvailability[];
}[];
products: {
name: string;
- regions: TRegion[];
+ regions: TRegionAvailability[];
}[];
};
diff --git a/packages/manager/modules/manager-pci-common/src/api/data/regions.ts b/packages/manager/modules/manager-pci-common/src/api/data/regions.ts
index cb9f3fa65fcb..d2972b060edb 100644
--- a/packages/manager/modules/manager-pci-common/src/api/data/regions.ts
+++ b/packages/manager/modules/manager-pci-common/src/api/data/regions.ts
@@ -1,4 +1,4 @@
-import { fetchIcebergV6 } from '@ovh-ux/manager-core-api';
+import { fetchIcebergV6, v6 } from '@ovh-ux/manager-core-api';
export type TRegion = {
name: string;
@@ -21,3 +21,13 @@ export const getProjectRegions = async (
});
return data;
};
+
+export const addProjectRegion = async (
+ projectId: string,
+ region: string,
+): Promise => {
+ const { data } = await v6.post(`/cloud/project/${projectId}/region`, {
+ region,
+ });
+ return data;
+};
diff --git a/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts b/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts
index 57ac5c298d61..09ba93337b30 100644
--- a/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts
+++ b/packages/manager/modules/manager-pci-common/src/api/hook/useAvailability.ts
@@ -1,4 +1,4 @@
-import { useQuery } from '@tanstack/react-query';
+import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useMe } from '@ovh-ux/manager-react-components';
import {
getProductAvailability,
@@ -28,3 +28,17 @@ export const useProductAvailability = (
enabled: !!me,
});
};
+
+export const useRefreshProductAvailability = (
+ projectId: string,
+ ovhSubsidiary: string,
+ filter?: ProductAvailabilityFilter,
+) => {
+ const queryClient = useQueryClient();
+ return {
+ refresh: () =>
+ queryClient.invalidateQueries({
+ queryKey: ['product-availability', projectId, ovhSubsidiary, filter],
+ }),
+ };
+};
diff --git a/packages/manager/modules/manager-pci-common/src/api/hook/useRegions.ts b/packages/manager/modules/manager-pci-common/src/api/hook/useRegions.ts
index 1a7c87c83d95..9b5ad22c697d 100644
--- a/packages/manager/modules/manager-pci-common/src/api/hook/useRegions.ts
+++ b/packages/manager/modules/manager-pci-common/src/api/hook/useRegions.ts
@@ -1,5 +1,5 @@
-import { useQuery } from '@tanstack/react-query';
-import { getProjectRegions } from '../data/regions';
+import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
+import { addProjectRegion, getProjectRegions } from '../data/regions';
export const useGetProjectRegionsQuery = (projectId: string) => ({
queryKey: ['project', projectId, 'regions'],
@@ -10,3 +10,31 @@ export const useGetProjectRegions = (projectId: string) =>
useQuery({
...useGetProjectRegionsQuery(projectId),
});
+
+export interface AddProjectRegionProps {
+ projectId: string;
+ onError: (cause: Error) => void;
+ onSuccess: () => void;
+}
+
+export const useAddProjectRegion = ({
+ projectId,
+ onError,
+ onSuccess,
+}: AddProjectRegionProps) => {
+ const queryClient = useQueryClient();
+ const mutation = useMutation({
+ mutationFn: (region: string) => addProjectRegion(projectId, region),
+ onError,
+ onSuccess: async () => {
+ await queryClient.invalidateQueries({
+ queryKey: ['project', projectId, 'regions'],
+ });
+ onSuccess();
+ },
+ });
+ return {
+ addRegion: (region: string) => mutation.mutate(region),
+ ...mutation,
+ };
+};