Skip to content

Commit bccd127

Browse files
committed
feat(obsto): add region mode
Signed-off-by: Lionel Bueno <[email protected]>
1 parent eea7983 commit bccd127

File tree

9 files changed

+615
-190
lines changed

9 files changed

+615
-190
lines changed

packages/manager/apps/pci-object-storage/public/assets/deploymentRegion/1AZ.svg

Lines changed: 9 additions & 0 deletions
Loading

packages/manager/apps/pci-object-storage/public/assets/deploymentRegion/3AZ.svg

Lines changed: 9 additions & 0 deletions
Loading

packages/manager/apps/pci-object-storage/public/assets/deploymentRegion/LZ.svg

Lines changed: 9 additions & 0 deletions
Loading

packages/manager/apps/pci-object-storage/public/translations/pci-object-storage/order-funnel/Messages_fr_FR.json

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
"labelEncryption": "Chiffrement vos données",
2121
"descriptionEncryption": "Les données déversées dans ce conteneur sont chiffrées à la volée par OVHcloud.",
2222
"summaryTitle": "Résumé",
23-
"pricingDisclaimer": "** Le prix affiché est une estimation pour 1 To d'Object Storage Standard pour 730 heures. Pour plus d'informations, <0>voir la page des prix</0>.",
24-
"orderButton": "Commander",
23+
"pricingDisclaimer": " Pour plus d'informations sur les tarifs, <0>voir la page des prix</0>.",
24+
"regionActivationInfo": "Si une région n'est pas encore disponible sur votre projet. Vous pouvez l'activer en suivant ce lien : <0>activer la région</0>.",
25+
"regionsNoMatch": "Aucune région ne correspond à vos filtres. Ajustez vos critères ou <0>activez une région</0>.",
26+
"orderButton": "Créer",
2527
"discoveryModeActivate": "Vous êtes actuellement en mode « Découverte ». Pour finaliser la création de votre conteneur, vous devez activer votre projet.",
2628
"discoveryModeActivateButton": "Activer votre projet",
2729
"summaryContainerSection": "Conteneur",
@@ -74,5 +76,13 @@
7476
"offsiteReplicationRegionPlaceholder": "Sélectionnez une localisation",
7577
"offsiteReplicationRegionSearchPlaceholder": "Chercher une localisation",
7678
"offsiteReplicationRegionhSearchNoResult": "Aucune localisation trouvée",
77-
"offsiteReplicationVersioningAlert": "En activant l’Offsite Replication, le versioning s’active également."
79+
"offsiteReplicationVersioningAlert": "En activant l'Offsite Replication, le versioning s'active également.",
80+
"pci_instances_common_instance_region_deployment_mode": "Région 1-AZ",
81+
"pci_instances_common_instance_region-3-az_deployment_mode": "Région 3-AZ",
82+
"pci_instances_common_instance_localzone_deployment_mode": "Local Zone",
83+
"pci_instances_common_instance_region_deployment_mode_description": "Déploiement résilient et économique sur 1 zone de disponibilité.",
84+
"pci_instances_common_instance_region-3-az_deployment_mode_description": "Déploiement haute résilience/haute disponibilité pour vos applications critiques sur 3 zones de disponibilité.",
85+
"pci_instances_common_instance_localzone_deployment_mode_description": "Déploiement de vos applications au plus près de vos utilisateurs pour une faible latence et la résidence des données.",
86+
"selectGeographicalZone": "Selectionnez une zone géographique",
87+
"showAllRegions": "Affichez toutes les régions"
7888
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { useTranslation } from 'react-i18next';
2+
import {
3+
Badge,
4+
Popover,
5+
PopoverTrigger,
6+
PopoverContent,
7+
} from '@datatr-ux/uxlib';
8+
import { ExternalLink, HelpCircle } from 'lucide-react';
9+
import { RegionTypeEnum } from '@datatr-ux/ovhcloud-types/cloud/index';
10+
11+
import A from '@/components/links/A.component';
12+
13+
const getBadgeConfig = (type: RegionTypeEnum) => {
14+
switch (type) {
15+
case RegionTypeEnum.region:
16+
return {
17+
label: '1-AZ',
18+
className: 'bg-primary-400 text-white',
19+
};
20+
case RegionTypeEnum['region-3-az']:
21+
return {
22+
label: '3-AZ',
23+
className: 'bg-primary-500 text-white',
24+
};
25+
case RegionTypeEnum.localzone:
26+
return {
27+
label: 'LocalZone',
28+
className: 'bg-primary-300 text-white',
29+
};
30+
default:
31+
return {
32+
label: '?',
33+
className: 'bg-neutral-100 text-text',
34+
};
35+
}
36+
};
37+
38+
interface RegionTypeBadgeProps {
39+
type: RegionTypeEnum;
40+
className?: string;
41+
}
42+
43+
export const RegionTypeBadge = ({ type, className }: RegionTypeBadgeProps) => {
44+
const config = getBadgeConfig(type);
45+
46+
return (
47+
<Badge className={className || config.className}>
48+
<span>{config.label}</span>
49+
</Badge>
50+
);
51+
};
52+
53+
interface RegionTypeBadgeWithPopoverProps extends RegionTypeBadgeProps {
54+
showPopover?: boolean;
55+
}
56+
57+
export const RegionTypeBadgeWithPopover = ({
58+
type,
59+
className,
60+
showPopover = true,
61+
}: RegionTypeBadgeWithPopoverProps) => {
62+
const { t } = useTranslation(['regions', 'pci-object-storage/order-funnel']);
63+
64+
const helpLink = (
65+
<A
66+
href="https://www.ovhcloud.com/fr/about-us/global-infrastructure/expansion-regions-az/"
67+
className="flex gap-1 items-center"
68+
target="_blank"
69+
rel="noreferrer noopener"
70+
>
71+
{t('help-link-more-info')}
72+
<ExternalLink className="size-3" />
73+
</A>
74+
);
75+
76+
const getDescription = (regionType: RegionTypeEnum) => {
77+
switch (regionType) {
78+
case RegionTypeEnum.region:
79+
return t('region-description-1AZ');
80+
case RegionTypeEnum['region-3-az']:
81+
return t('region-description-3AZ');
82+
case RegionTypeEnum.localzone:
83+
return t('region-description-localzone');
84+
default:
85+
return '';
86+
}
87+
};
88+
89+
if (!showPopover) {
90+
return <RegionTypeBadge type={type} className={className} />;
91+
}
92+
93+
const config = getBadgeConfig(type);
94+
const description = getDescription(type);
95+
96+
return (
97+
<Badge className={className || config.className}>
98+
<span>{config.label}</span>
99+
{description && (
100+
<Popover>
101+
<PopoverTrigger asChild>
102+
<HelpCircle className="size-4 ml-2" />
103+
</PopoverTrigger>
104+
<PopoverContent className="text-sm">
105+
{description}
106+
{helpLink}
107+
</PopoverContent>
108+
</Popover>
109+
)}
110+
</Badge>
111+
);
112+
};

packages/manager/apps/pci-object-storage/src/pages/object-storage/create/_components/OrderPricing.component.tsx

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import order from '@/types/Order';
55

66
const HOUR_IN_MONTH = 730;
77
const MEGA_BYTES = 1024;
8+
89
const OrderPricing = ({
910
pricings,
1011
}: {
@@ -42,36 +43,6 @@ const OrderPricing = ({
4243
</TableCell>
4344
</TableRow>
4445
)}
45-
{pricings.replication && (
46-
<TableRow className="text-xs">
47-
<TableCell className="px-0 align-top">
48-
{t('pricing_option_replication_label')}
49-
</TableCell>
50-
<TableCell className="text-right px-0">
51-
<Price
52-
className="flex flex-row justify-end items-center flex-wrap gap-2"
53-
decimals={2}
54-
priceInUcents={toHourlyTo(
55-
pricings.replication.pricings[0].price,
56-
)}
57-
taxInUcents={toHourlyTo(pricings.replication.pricings[0].tax)}
58-
/>
59-
</TableCell>
60-
</TableRow>
61-
)}
62-
<TableRow>
63-
<TableCell className="font-semibold text-text px-0 align-top">
64-
{t('total_monthly_label')}
65-
</TableCell>
66-
<TableCell className="text-right px-0">
67-
<Price
68-
className="flex flex-row justify-end items-center flex-wrap gap-2"
69-
decimals={2}
70-
priceInUcents={toHourlyTo(total.price)}
71-
taxInUcents={toHourlyTo(total.tax)}
72-
/>
73-
</TableCell>
74-
</TableRow>
7546
</TableBody>
7647
</Table>
7748
</div>
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { Card, CardHeader, Checkbox } from '@datatr-ux/uxlib';
2+
import { useTranslation } from 'react-i18next';
3+
import cloud from '@/types/Cloud';
4+
import { cn } from '@/lib/utils';
5+
import { RegionTypeBadge } from './RegionTypeBadge.component';
6+
7+
type TDeploymentMode = {
8+
mode: cloud.RegionTypeEnum;
9+
title: string;
10+
description: string;
11+
Image: () => JSX.Element;
12+
isDefaultActive?: boolean;
13+
};
14+
15+
type TDeploymentModeConfig = {
16+
mode: cloud.RegionTypeEnum;
17+
imagePath: string;
18+
isDefaultActive: boolean;
19+
};
20+
21+
export type TDeploymentModeSelectionProps = {
22+
value: cloud.RegionTypeEnum[];
23+
onChange: (modes: cloud.RegionTypeEnum[]) => void;
24+
};
25+
26+
const DEPLOYMENT_MODES_CONFIG: TDeploymentModeConfig[] = [
27+
{
28+
mode: cloud.RegionTypeEnum['region-3-az'],
29+
imagePath: 'assets/deploymentRegion/3AZ.svg',
30+
isDefaultActive: true,
31+
},
32+
{
33+
mode: cloud.RegionTypeEnum.region,
34+
imagePath: 'assets/deploymentRegion/1AZ.svg',
35+
isDefaultActive: true,
36+
},
37+
{
38+
mode: cloud.RegionTypeEnum.localzone,
39+
imagePath: 'assets/deploymentRegion/LZ.svg',
40+
isDefaultActive: false,
41+
},
42+
];
43+
44+
export const getDefaultDeploymentModes = (): cloud.RegionTypeEnum[] => {
45+
return DEPLOYMENT_MODES_CONFIG.filter((config) => config.isDefaultActive).map(
46+
(config) => config.mode,
47+
);
48+
};
49+
50+
const Icon = ({
51+
imagePath,
52+
width = 288,
53+
height = 170,
54+
}: {
55+
imagePath: string;
56+
width?: number;
57+
height?: number;
58+
}) => (
59+
<div
60+
style={{
61+
backgroundImage: `url('${imagePath}')`,
62+
backgroundRepeat: 'no-repeat',
63+
backgroundSize: 'contain',
64+
width: `${width}px`,
65+
height: `${height}px`,
66+
}}
67+
className={cn('inline-block align-middle shadow-sm')}
68+
/>
69+
);
70+
71+
export const DeploymentModeSelection = ({
72+
value,
73+
onChange,
74+
}: TDeploymentModeSelectionProps) => {
75+
const { t } = useTranslation('pci-object-storage/order-funnel');
76+
77+
const getTranslationKey = (
78+
mode: cloud.RegionTypeEnum,
79+
type: 'title' | 'description',
80+
): string => {
81+
const modeKey = mode;
82+
const suffix =
83+
type === 'title' ? 'deployment_mode' : 'deployment_mode_description';
84+
return `pci_instances_common_instance_${modeKey}_${suffix}`;
85+
};
86+
87+
const deploymentModes: TDeploymentMode[] = DEPLOYMENT_MODES_CONFIG.map(
88+
(config) => ({
89+
mode: config.mode,
90+
title: t(getTranslationKey(config.mode, 'title')),
91+
description: t(getTranslationKey(config.mode, 'description')),
92+
Image: () => (
93+
<Icon imagePath={config.imagePath} width={120} height={80} />
94+
),
95+
isDefaultActive: config.isDefaultActive,
96+
}),
97+
);
98+
99+
const handleSelect = (selectedMode: cloud.RegionTypeEnum) => () => {
100+
const currentValue = value || [];
101+
const isSelected = value?.some((item) => item === selectedMode) || false;
102+
103+
const selection = isSelected
104+
? currentValue.filter((mode) => mode !== selectedMode)
105+
: [...currentValue, selectedMode];
106+
107+
onChange(selection);
108+
};
109+
110+
return (
111+
<section>
112+
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
113+
{deploymentModes.map((deploymentMode) => {
114+
const { mode, title, description, Image } = deploymentMode;
115+
const isSelected = value?.some((item) => item === mode);
116+
117+
return (
118+
<button
119+
data-state={isSelected ? 'checked' : ''}
120+
className={cn(
121+
'text-left p-4 rounded-md bg-card text-card-foreground data-[state=checked]:border-primary data-[state=checked]:bg-primary-50 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 border border-border data-[state=checked]:shadow-[0_0_0_1px] flex flex-col gap-2',
122+
)}
123+
key={mode}
124+
onClick={handleSelect(mode)}
125+
>
126+
<div className="flex flex-row items-center gap-4 ">
127+
<div className="flex justify-between w-full">
128+
<div>
129+
<div className="flex items-center gap-2">
130+
<Checkbox checked={isSelected} />
131+
<p className="font-bold text-sm text-[--ods-color-heading]">
132+
{title}
133+
</p>
134+
<RegionTypeBadge type={mode} />
135+
</div>
136+
<div className="text-xs flex-1 flex flex-col mt-3">
137+
{description}
138+
</div>
139+
</div>
140+
<div>
141+
<Image />
142+
</div>
143+
</div>
144+
</div>
145+
</button>
146+
);
147+
})}
148+
</div>
149+
</section>
150+
);
151+
};

0 commit comments

Comments
 (0)