Skip to content

Commit

Permalink
feat(ips): region-selector component
Browse files Browse the repository at this point in the history
ref: MANAGER-16479

Signed-off-by: Nicolas Pierre-charles <[email protected]>
  • Loading branch information
Nicolas Pierre-charles committed Dec 20, 2024
1 parent d903b70 commit 8573e22
Show file tree
Hide file tree
Showing 8 changed files with 1,313 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/manager/apps/ips/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
coverage
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{
"region-selector-all-locations": "Toutes les regions",
"region-selector-eu-filter": "Europe",
"region-selector-ca-filter": "Canada",
"region-selector-us-filter": "USA",
"region-selector-country-code_eu-west-par": "fr",
"region-selector-country-name_eu-west-par": "France",
"region-selector-city-name_eu-west-par": "Paris",
"region-selector-country-code_eu-west-gra": "fr",
"region-selector-country-name_eu-west-gra": "France",
"region-selector-city-name_eu-west-gra": "Gravelines",
"region-selector-country-code_eu-west-rbx": "fr",
"region-selector-country-name_eu-west-rbx": "France",
"region-selector-city-name_eu-west-rbx": "Roubaix",
"region-selector-country-code_eu-west-sbg": "fr",
"region-selector-country-name_eu-west-sbg": "France",
"region-selector-city-name_eu-west-sbg": "Strasbourg",
"region-selector-country-code_eu-west-lim": "fr",
"region-selector-country-name_eu-west-lim": "Allemagne",
"region-selector-city-name_eu-west-lim": "Limburg",
"region-selector-country-code_eu-west-eri": "gb",
"region-selector-country-name_eu-west-eri": "Royaume-Uni",
"region-selector-city-name_eu-west-eri": "Erith",
"region-selector-country-code_eu-central-waw": "pl",
"region-selector-country-name_eu-central-waw": "Pologne",
"region-selector-city-name_eu-central-waw": "Varsovie",
"region-selector-country-code_eu-west-lz-bru": "be",
"region-selector-country-name_eu-west-lz-bru": "Belgique",
"region-selector-city-name_eu-west-lz-bru": "Bruxelles (lz)",
"region-selector-country-code_eu-west-lz-mad": "es",
"region-selector-country-name_eu-west-lz-mad": "Espagne",
"region-selector-city-name_eu-west-lz-mad": "Madrid (lz)",
"region-selector-country-code_eu-west-gra-snc": "fr",
"region-selector-country-name_eu-west-gra-snc": "France",
"region-selector-city-name_eu-west-gra-snc": "Gravelines (snc)",
"region-selector-country-code_eu-west-rbx-snc": "fr",
"region-selector-country-name_eu-west-rbx-snc": "France",
"region-selector-city-name_eu-west-rbx-snc": "Roubaix (snc)",
"region-selector-country-code_eu-west-sbg-snc": "fr",
"region-selector-country-name_eu-west-sbg-snc": "France",
"region-selector-city-name_eu-west-sbg-snc": "Strasbourg (snc)",
"region-selector-country-code_eu-west-lz-mrs": "fr",
"region-selector-country-name_eu-west-lz-mrs": "France",
"region-selector-city-name_eu-west-lz-mrs": "Marseille",
"region-selector-country-code_labeu-west-1-preprod": "fr",
"region-selector-country-name_labeu-west-1-preprod": "France",
"region-selector-city-name_labeu-west-1-preprod": "Croix (preprod)",
"region-selector-country-code_labeu-west-1-dev-1": "fr",
"region-selector-country-name_labeu-west-1-dev-1": "France",
"region-selector-city-name_labeu-west-1-dev-1": "Croix (dev-1)",
"region-selector-country-code_labeu-west-1-dev-2": "fr",
"region-selector-country-name_labeu-west-1-dev-2": "France",
"region-selector-city-name_labeu-west-1-dev-2": "Croix (dev-2)",
"region-selector-country-code_us-east-vin": "us",
"region-selector-country-name_us-east-vin": "USA",
"region-selector-city-name_us-east-vin": "Vinthill",
"region-selector-country-code_us-west-hil": "us",
"region-selector-country-name_us-west-hil": "USA",
"region-selector-city-name_us-west-hil": "Hillsboro",
"region-selector-country-code_us-east-lz-dal": "us",
"region-selector-country-name_us-east-lz-dal": "USA",
"region-selector-city-name_us-east-lz-dal": "Dallas",
"region-selector-country-code_us-west-lz-lax": "us",
"region-selector-country-name_us-west-lz-lax": "USA",
"region-selector-city-name_us-west-lz-lax": "Los Angeles",
"region-selector-country-code_us-east-lz-chi": "us",
"region-selector-country-name_us-east-lz-chi": "USA",
"region-selector-city-name_us-east-lz-chi": "Chicago",
"region-selector-country-code_us-east-lz-nyc": "us",
"region-selector-country-name_us-east-lz-nyc": "USA",
"region-selector-city-name_us-east-lz-nyc": "New York",
"region-selector-country-code_us-east-lz-mia": "us",
"region-selector-country-name_us-east-lz-mia": "USA",
"region-selector-city-name_us-east-lz-mia": "Miami",
"region-selector-country-code_us-west-lz-pao": "us",
"region-selector-country-name_us-west-lz-pao": "USA",
"region-selector-city-name_us-west-lz-pao": "Palo Alto",
"region-selector-country-code_us-west-lz-den": "us",
"region-selector-country-name_us-west-lz-den": "USA",
"region-selector-city-name_us-west-lz-den": "Denver",
"region-selector-country-code_us-east-lz-atl": "us",
"region-selector-country-name_us-east-lz-atl": "USA",
"region-selector-city-name_us-east-lz-atl": "Atlanta",
"region-selector-country-code_ca-east-bhs": "ca",
"region-selector-country-name_ca-east-bhs": "Canada",
"region-selector-city-name_ca-east-bhs": "Beauharnois",
"region-selector-country-code_ca-east-tor": "ca",
"region-selector-country-name_ca-east-tor": "Canada",
"region-selector-city-name_ca-east-tor": "Toronto",
"region-selector-country-code_ap-southeast-sgp": "sg",
"region-selector-country-name_ap-southeast-sgp": "Singapour",
"region-selector-city-name_ap-southeast-sgp": "Singapour",
"region-selector-country-code_ap-southeast-syd": "au",
"region-selector-country-name_ap-southeast-syd": "Australie",
"region-selector-city-name_ap-southeast-syd": "Sydney",
"region-selector-country-code_ap-south-mum": "in",
"region-selector-country-name_ap-south-mum": "Inde",
"region-selector-city-name_ap-south-mum": "Mumbai"
}
3 changes: 0 additions & 3 deletions packages/manager/apps/ips/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import React, { useEffect, useContext } from 'react';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { odsSetup } from '@ovhcloud/ods-common-core';
import { ShellContext } from '@ovh-ux/manager-react-shell-client';
import { RouterProvider, createHashRouter } from 'react-router-dom';
import { Routes } from './routes/routes';

odsSetup();

const queryClient = new QueryClient({
defaultOptions: {
queries: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import { describe, it, vi, expect } from 'vitest';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { RegionSelector } from './RegionSelector';
import '@testing-library/jest-dom';

vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (translationKey: string) => translationKey,
}),
}));

const regionList = [
'eu-west-par',
'eu-west-gra',
'eu-west-rbx',
'eu-west-sbg',
'eu-west-lim',
'eu-central-waw',
'eu-west-eri',
'us-east-vin',
'us-west-hil',
'ca-east-bhs',
'ap-southeast-sgp',
'ap-southeast-syd',
'eu-west-rbx-snc',
'eu-west-sbg-snc',
'ca-east-tor',
'ap-south-mum',
'labeu-west-1-preprod',
'labeu-west-1-dev-2',
'labeu-west-1-dev-1',
'eu-west-lz-bru',
'eu-west-lz-mad',
'eu-west-gra-snc',
'us-east-lz-dal',
'us-west-lz-lax',
'us-east-lz-chi',
'us-east-lz-nyc',
'us-east-lz-mia',
'us-west-lz-pao',
'us-west-lz-den',
'us-east-lz-atl',
'eu-west-lz-mrs',
];

describe('RegionSelector component', () => {
it('renders correctly', async () => {
const spy = vi.fn();
const { asFragment, container, getByText } = render(
<RegionSelector
selectedRegion="eu-west-gra"
regionList={regionList}
setSelectedRegion={spy}
/>,
);

expect(container.querySelectorAll('ods-card')).toHaveLength(
regionList.length,
);
expect(asFragment()).toMatchSnapshot();

Check failure on line 62 in packages/manager/apps/ips/src/components/RegionSelector/RegionSelector.spec.tsx

View workflow job for this annotation

GitHub Actions / test

src/components/RegionSelector/RegionSelector.spec.tsx > RegionSelector component > renders correctly

Error: Snapshot `RegionSelector component > renders correctly 1` mismatched - Expected + Received @@ -29,11 +29,11 @@ </ods-tabs> <div class="flex flex-wrap gap-4" > <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-west-par w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -57,11 +57,11 @@ eu-west-par </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer border-[2px] border-[#0050D7] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer border-[2px] border-[#0050D7] hover:border-[#0050D7] w-full sm:w-[245px]" color="primary" > <span class="fi-region-selector-country-code_eu-west-gra w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -85,11 +85,11 @@ eu-west-gra </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-west-rbx w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -113,11 +113,11 @@ eu-west-rbx </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-west-sbg w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -141,11 +141,11 @@ eu-west-sbg </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-west-lim w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -169,11 +169,11 @@ eu-west-lim </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-central-waw w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -197,11 +197,11 @@ eu-central-waw </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span class="fi-region-selector-country-code_eu-west-eri w-[44px] h-[32px] shadow-md shadow-[#000E9C33] mr-3" /> @@ -225,11 +225,11 @@ eu-west-eri </ods-text> </div> </ods-card> <ods-card - class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-[245px]" + class="flex items-center p-3 cursor-pointer m-[1px] hover:border-[#0050D7] w-full sm:w-[245px]" color="neutral" > <span cl

const newRegion = 'us-west-lz-den';
await waitFor(() => userEvent.click(getByText(newRegion)));
expect(spy).toHaveBeenCalledWith(newRegion);
});

it.each([
{ label: 'region-selector-eu-filter', cardNb: 16 },
{ label: 'region-selector-ca-filter', cardNb: 2 },
{ label: 'region-selector-us-filter', cardNb: 10 },
])('filters correctly', async ({ label, cardNb }) => {
const { getByText, container } = render(
<RegionSelector regionList={regionList} setSelectedRegion={vi.fn()} />,
);

const filterTab = getByText(label);
await waitFor(() => userEvent.click(filterTab));

expect(container.querySelectorAll('ods-card')).toHaveLength(cardNb);

await waitFor(() =>
userEvent.click(getByText('region-selector-all-locations')),
);
expect(container.querySelectorAll('ods-card')).toHaveLength(
regionList.length,
);
});

it('does not break if there is no region at all', () => {
const { getByText } = render(
<RegionSelector regionList={[]} setSelectedRegion={vi.fn()} />,
);

expect(getByText('region-selector-all-locations')).toBeInTheDocument();
});

it.each([
{
list: ['us-west-lz-lax', 'us-east-lz-chi', 'us-east-lz-nyc'],
disabledFilters: ['eu', 'ca'],
},
{
list: ['eu-west-par', 'eu-west-gra', 'eu-west-rbx'],
disabledFilters: ['us', 'ca'],
},
{
list: [
'eu-west-par',
'eu-west-gra',
'eu-west-rbx',
'ca-east-tor',
'ap-south-mum',
],
disabledFilters: ['us'],
},
])(
'disable filters if there is no corresponding regions',
({ list, disabledFilters }) => {
const { getByText } = render(
<RegionSelector regionList={list} setSelectedRegion={vi.fn()} />,
);

disabledFilters.forEach((tab) => {
expect(getByText(`region-selector-${tab}-filter`)).toHaveAttribute(
'is-disabled',
'true',
);
});
},
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import React from 'react';
import { OdsCard, OdsText } from '@ovhcloud/ods-components/react';
import { ODS_CARD_COLOR, ODS_TEXT_PRESET } from '@ovhcloud/ods-components';
import { useTranslation } from 'react-i18next';
import { RegionTabs } from './RegionTabs';
import {
RegionFilter,
isRegionInCa,
isRegionInEu,
isRegionInUs,
selectedBorderColor,
shadowColor,
} from './regionSelector.utils';
import 'flag-icons/css/flag-icons.min.css';

export type RegionSelectorProps = {
regionList: string[];
selectedRegion?: string;
setSelectedRegion: React.Dispatch<React.SetStateAction<string>>;
};

export const RegionSelector: React.FC<RegionSelectorProps> = ({
regionList,
selectedRegion,
setSelectedRegion,
}) => {
const [currentFilter, setCurrentFilter] = React.useState(RegionFilter.all);
const { t } = useTranslation('region-selector');
return (
<div>
<RegionTabs
regionList={regionList}
currentFilter={currentFilter}
removeFilter={() => setCurrentFilter(RegionFilter.all)}
setCaFilter={() => setCurrentFilter(RegionFilter.ca)}
setEuFilter={() => setCurrentFilter(RegionFilter.eu)}
setUsFilter={() => setCurrentFilter(RegionFilter.us)}
/>
<div className="flex flex-wrap gap-4">
{regionList
.filter((region) => {
switch (currentFilter) {
case RegionFilter.eu:
return isRegionInEu(region);
case RegionFilter.ca:
return isRegionInCa(region);
case RegionFilter.us:
return isRegionInUs(region);
case RegionFilter.all:
default:
return true;
}
})
.map((region) => {
const countryCode = t(`region-selector-country-code_${region}`);
const isSelected = selectedRegion === region;
const color = isSelected
? ODS_CARD_COLOR.primary
: ODS_CARD_COLOR.neutral;
const borderStyle = isSelected
? `border-[2px] border-[${selectedBorderColor}]`
: 'm-[1px]';

return (
<OdsCard
key={region}
className={`flex items-center p-3 cursor-pointer ${borderStyle} hover:border-[${selectedBorderColor}] w-full sm:w-[245px]`}
onClick={() => setSelectedRegion(region)}
color={color}
>
<span
className={`fi-${countryCode} w-[44px] h-[32px] shadow-md shadow-[${shadowColor}] mr-3`}
/>
<div className="flex flex-col">
<OdsText className="block" preset={ODS_TEXT_PRESET.heading4}>
{t(`region-selector-country-name_${region}`)}
</OdsText>
<OdsText preset={ODS_TEXT_PRESET.span}>
{t(`region-selector-city-name_${region}`)}
</OdsText>
<OdsText preset={ODS_TEXT_PRESET.span}>{region}</OdsText>
</div>
</OdsCard>
);
})}
</div>
</div>
);
};

export default RegionSelector;
Loading

0 comments on commit 8573e22

Please sign in to comment.