Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
"labelEncryption": "Chiffrement vos données",
"descriptionEncryption": "Les données déversées dans ce conteneur sont chiffrées à la volée par OVHcloud.",
"summaryTitle": "Résumé",
"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>.",
"orderButton": "Commander",
"pricingDisclaimer": " Pour plus d'informations sur les tarifs, <0>voir la page des prix</0>.",
"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>.",
"regionsNoMatch": "Aucune région ne correspond à vos filtres. Ajustez vos critères ou <0>activez une région</0>.",
"orderButton": "Créer",
"discoveryModeActivate": "Vous êtes actuellement en mode « Découverte ». Pour finaliser la création de votre conteneur, vous devez activer votre projet.",
"discoveryModeActivateButton": "Activer votre projet",
"summaryContainerSection": "Conteneur",
Expand Down Expand Up @@ -74,5 +76,13 @@
"offsiteReplicationRegionPlaceholder": "Sélectionnez une localisation",
"offsiteReplicationRegionSearchPlaceholder": "Chercher une localisation",
"offsiteReplicationRegionhSearchNoResult": "Aucune localisation trouvée",
"offsiteReplicationVersioningAlert": "En activant l’Offsite Replication, le versioning s’active également."
"offsiteReplicationVersioningAlert": "En activant l'Offsite Replication, le versioning s'active également.",
"pci_instances_common_instance_region_deployment_mode": "Région 1-AZ",
"pci_instances_common_instance_region-3-az_deployment_mode": "Région 3-AZ",
"pci_instances_common_instance_localzone_deployment_mode": "Local Zone",
"pci_instances_common_instance_region_deployment_mode_description": "Déploiement résilient et économique sur 1 zone de disponibilité.",
"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é.",
"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.",
"selectGeographicalZone": "Selectionnez une zone géographique",
"showAllRegions": "Affichez toutes les régions"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { useTranslation } from 'react-i18next';
import {
Badge,
Popover,
PopoverTrigger,
PopoverContent,
} from '@datatr-ux/uxlib';
import { ExternalLink, HelpCircle } from 'lucide-react';
import { RegionTypeEnum } from '@datatr-ux/ovhcloud-types/cloud/index';

import A from '@/components/links/A.component';

const getBadgeConfig = (type: RegionTypeEnum) => {
switch (type) {
case RegionTypeEnum.region:
return {
label: '1-AZ',
className: 'bg-primary-400 text-white',
};
case RegionTypeEnum['region-3-az']:
return {
label: '3-AZ',
className: 'bg-primary-500 text-white',
};
case RegionTypeEnum.localzone:
return {
label: 'LocalZone',
className: 'bg-primary-300 text-white',
};
default:
return {
label: '?',
className: 'bg-neutral-100 text-text',
};
}
};

interface RegionTypeBadgeProps {
type: RegionTypeEnum;
className?: string;
}

export const RegionTypeBadge = ({ type, className }: RegionTypeBadgeProps) => {
const config = getBadgeConfig(type);

return (
<Badge className={className || config.className}>
<span>{config.label}</span>
</Badge>
);
};

interface RegionTypeBadgeWithPopoverProps extends RegionTypeBadgeProps {
showPopover?: boolean;
}

export const RegionTypeBadgeWithPopover = ({
type,
className,
showPopover = true,
}: RegionTypeBadgeWithPopoverProps) => {
const { t } = useTranslation(['regions', 'pci-object-storage/order-funnel']);

const helpLink = (
<A
href="https://www.ovhcloud.com/fr/about-us/global-infrastructure/expansion-regions-az/"
className="flex gap-1 items-center"
target="_blank"
rel="noreferrer noopener"
>
{t('help-link-more-info')}
<ExternalLink className="size-3" />
</A>
);

const getDescription = (regionType: RegionTypeEnum) => {
switch (regionType) {
case RegionTypeEnum.region:
return t('region-description-1AZ');
case RegionTypeEnum['region-3-az']:
return t('region-description-3AZ');
case RegionTypeEnum.localzone:
return t('region-description-localzone');
default:
return '';
}
};

if (!showPopover) {
return <RegionTypeBadge type={type} className={className} />;
}

const config = getBadgeConfig(type);
const description = getDescription(type);

return (
<Badge className={className || config.className}>
<span>{config.label}</span>
{description && (
<Popover>
<PopoverTrigger asChild>
<HelpCircle className="size-4 ml-2" />
</PopoverTrigger>
<PopoverContent className="text-sm">
{description}
{helpLink}
</PopoverContent>
</Popover>
)}
</Badge>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import order from '@/types/Order';

const HOUR_IN_MONTH = 730;
const MEGA_BYTES = 1024;

const OrderPricing = ({
pricings,
}: {
Expand Down Expand Up @@ -42,36 +43,6 @@ const OrderPricing = ({
</TableCell>
</TableRow>
)}
{pricings.replication && (
<TableRow className="text-xs">
<TableCell className="px-0 align-top">
{t('pricing_option_replication_label')}
</TableCell>
<TableCell className="text-right px-0">
<Price
className="flex flex-row justify-end items-center flex-wrap gap-2"
decimals={2}
priceInUcents={toHourlyTo(
pricings.replication.pricings[0].price,
)}
taxInUcents={toHourlyTo(pricings.replication.pricings[0].tax)}
/>
</TableCell>
</TableRow>
)}
<TableRow>
<TableCell className="font-semibold text-text px-0 align-top">
{t('total_monthly_label')}
</TableCell>
<TableCell className="text-right px-0">
<Price
className="flex flex-row justify-end items-center flex-wrap gap-2"
decimals={2}
priceInUcents={toHourlyTo(total.price)}
taxInUcents={toHourlyTo(total.tax)}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { Card, CardHeader, Checkbox } from '@datatr-ux/uxlib';
import { useTranslation } from 'react-i18next';
import cloud from '@/types/Cloud';
import { cn } from '@/lib/utils';
import { RegionTypeBadge } from './RegionTypeBadge.component';

type TDeploymentMode = {
mode: cloud.RegionTypeEnum;
title: string;
description: string;
Image: () => JSX.Element;
isDefaultActive?: boolean;
};

type TDeploymentModeConfig = {
mode: cloud.RegionTypeEnum;
imagePath: string;
isDefaultActive: boolean;
};

export type TDeploymentModeSelectionProps = {
value: cloud.RegionTypeEnum[];
onChange: (modes: cloud.RegionTypeEnum[]) => void;
};

const DEPLOYMENT_MODES_CONFIG: TDeploymentModeConfig[] = [
{
mode: cloud.RegionTypeEnum['region-3-az'],
imagePath: 'assets/deploymentRegion/3AZ.svg',
isDefaultActive: true,
},
{
mode: cloud.RegionTypeEnum.region,
imagePath: 'assets/deploymentRegion/1AZ.svg',
isDefaultActive: true,
},
{
mode: cloud.RegionTypeEnum.localzone,
imagePath: 'assets/deploymentRegion/LZ.svg',
isDefaultActive: false,
},
];

export const getDefaultDeploymentModes = (): cloud.RegionTypeEnum[] => {
return DEPLOYMENT_MODES_CONFIG.filter((config) => config.isDefaultActive).map(
(config) => config.mode,
);
};

const Icon = ({
imagePath,
width = 288,
height = 170,
}: {
imagePath: string;
width?: number;
height?: number;
}) => (
<div
style={{
backgroundImage: `url('${imagePath}')`,
backgroundRepeat: 'no-repeat',
backgroundSize: 'contain',
width: `${width}px`,
height: `${height}px`,
}}
className={cn('inline-block align-middle shadow-sm')}
/>
);

export const DeploymentModeSelection = ({
value,
onChange,
}: TDeploymentModeSelectionProps) => {
const { t } = useTranslation('pci-object-storage/order-funnel');

const getTranslationKey = (
mode: cloud.RegionTypeEnum,
type: 'title' | 'description',
): string => {
const modeKey = mode;
const suffix =
type === 'title' ? 'deployment_mode' : 'deployment_mode_description';
return `pci_instances_common_instance_${modeKey}_${suffix}`;
};

const deploymentModes: TDeploymentMode[] = DEPLOYMENT_MODES_CONFIG.map(
(config) => ({
mode: config.mode,
title: t(getTranslationKey(config.mode, 'title')),
description: t(getTranslationKey(config.mode, 'description')),
Image: () => (
<Icon imagePath={config.imagePath} width={120} height={80} />
),
isDefaultActive: config.isDefaultActive,
}),
);

const handleSelect = (selectedMode: cloud.RegionTypeEnum) => () => {
const currentValue = value || [];
const isSelected = value?.some((item) => item === selectedMode) || false;

const selection = isSelected
? currentValue.filter((mode) => mode !== selectedMode)
: [...currentValue, selectedMode];

onChange(selection);
};

return (
<section>
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-4">
{deploymentModes.map((deploymentMode) => {
const { mode, title, description, Image } = deploymentMode;
const isSelected = value?.some((item) => item === mode);

return (
<button
data-state={isSelected ? 'checked' : ''}
className={cn(
'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',
)}
key={mode}
onClick={handleSelect(mode)}
>
<div className="flex flex-row items-center gap-4 ">
<div className="flex justify-between w-full">
<div>
<div className="flex items-center gap-2">
<Checkbox checked={isSelected} />
<p className="font-bold text-sm text-[--ods-color-heading]">
{title}
</p>
<RegionTypeBadge type={mode} />
</div>
<div className="text-xs flex-1 flex flex-col mt-3">
{description}
</div>
</div>
<div>
<Image />
</div>
</div>
</div>
</button>
);
})}
</div>
</section>
);
};
Loading
Loading