Skip to content
Open
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
13 changes: 13 additions & 0 deletions packages/manager/apps/pci-project/src/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,16 @@ ods-text.disabled::part(text) {
flex-grow: 1;
max-width: 33.3333%;
}

@media (max-width: 35rem) {
.contacts-card .row {
flex-direction: column;
gap: 0.5em;
}
.contacts-card .contacts-card-info {
max-width: 100%;
&:first-child {
align-self: flex-end;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
BaseLayout,
Notifications,
ChangelogButton,
RedirectionGuard,
PciGuidesHeader,
Datagrid,
useProjectUrl,
Expand Down Expand Up @@ -65,7 +66,11 @@ export default function ContactsPage() {
const context = useContext(ShellContext);
const user = context.environment.getUser();
const projectId = useParam('projectId');
const { data: project } = useProject();
const {
data: project,
isLoading: isProjectLoading,
isError: isProjectError,
} = useProject();
const { data: serviceInfo } = useProjectService(projectId);
const { data: aclAccountIds } = useProjectAcl(projectId);
const userAcls = useProjectAclAccountsInfo(projectId, aclAccountIds || []);
Expand All @@ -78,96 +83,111 @@ export default function ContactsPage() {
const openAddContactModal = () => {
navigate(`./${urls.contactAndRightsAdd}`);
};
const isUsRegion = context.environment.getRegion() === 'US';
const isEuRegion = context.environment.getRegion() === 'EU';
const discoveryProject = isDiscoveryProject(project);
const canChangeContacts = isEuRegion && !discoveryProject;
const contactsPageHref = useGetContactsPageHref(context, serviceInfo);
const redirectToProjects = isProjectError || isUsRegion;

if (!project || !serviceInfo || !userAcls) {
return <OdsSpinner size="md" />;
return (
<RedirectionGuard
condition={redirectToProjects}
isLoading={isProjectLoading}
route={urls.root}
>
<OdsSpinner size="md" />
</RedirectionGuard>
);
}

return (
<BaseLayout
header={{
title: t('pci_projects_project_contacts_title'),
changelogButton: <ChangelogButton links={ROADMAP_CHANGELOG_LINKS} />,
headerButton: <PciGuidesHeader category="instances" />,
}}
breadcrumb={
<OdsBreadcrumb>
<OdsBreadcrumbItem href={hrefProject} label={project.description} />
<OdsBreadcrumbItem
href={'#'}
label={t('pci_projects_project_contacts_title')}
/>
</OdsBreadcrumb>
}
<RedirectionGuard
condition={redirectToProjects}
isLoading={isProjectLoading}
route={urls.root}
>
<div className="flex flex-col gap-4 mb-8">
<PciDiscoveryBanner project={project} />
<Notifications />
<div className="contacts-card">
<div className="row">
<OdsText className="contacts-card-info text-right">
{t('cpb_rights_owner')}
</OdsText>
<OdsText className="contacts-card-info text-left">
{serviceInfo.contactAdmin}
</OdsText>
<div className="contacts-card-info">
{canChangeContacts && (
<OdsLink
href={contactsPageHref}
label={t('cpb_rights_modify')}
icon={ODS_ICON_NAME.externalLink}
target="_blank"
/>
)}
<BaseLayout
header={{
title: t('pci_projects_project_contacts_title'),
changelogButton: <ChangelogButton links={ROADMAP_CHANGELOG_LINKS} />,
headerButton: <PciGuidesHeader category="instances" />,
}}
breadcrumb={
<OdsBreadcrumb>
<OdsBreadcrumbItem href={hrefProject} label={project.description} />
<OdsBreadcrumbItem
href={'#'}
label={t('pci_projects_project_contacts_title')}
/>
</OdsBreadcrumb>
}
>
<div className="flex flex-col gap-4 mb-8">
<PciDiscoveryBanner project={project} />
<Notifications />
<div className="contacts-card">
<div className="row">
<OdsText className="contacts-card-info text-right">
{t('cpb_rights_owner')}
</OdsText>
<OdsText className="contacts-card-info text-left">
{serviceInfo.contactAdmin}
</OdsText>
<div className="contacts-card-info">
{canChangeContacts && (
<OdsLink
href={contactsPageHref}
label={t('cpb_rights_modify')}
icon={ODS_ICON_NAME.externalLink}
target="_blank"
/>
)}
</div>
</div>
</div>
<div className="row mt-4">
<OdsText className="contacts-card-info text-right">
{t('cpb_rights_billing_contact')}
</OdsText>
<OdsText className="contacts-card-info text-left">
{serviceInfo.contactBilling}
</OdsText>
<div className="contacts-card-info">
{canChangeContacts && (
<OdsLink
href={contactsPageHref}
label={t('cpb_rights_modify')}
icon={ODS_ICON_NAME.externalLink}
target="_blank"
/>
)}
<div className="row mt-4">
<OdsText className="contacts-card-info text-right">
{t('cpb_rights_billing_contact')}
</OdsText>
<OdsText className="contacts-card-info text-left">
{serviceInfo.contactBilling}
</OdsText>
<div className="contacts-card-info">
{canChangeContacts && (
<OdsLink
href={contactsPageHref}
label={t('cpb_rights_modify')}
icon={ODS_ICON_NAME.externalLink}
target="_blank"
/>
)}
</div>
</div>
</div>
</div>
<OdsText>{t('cpb_rights_expl2')}</OdsText>
<OdsText>{t('cpb_rights_note')}</OdsText>
<OdsText>{t('cpb_rights_expl2')}</OdsText>
<OdsText>{t('cpb_rights_note')}</OdsText>

{isAdmin && (
<OdsButton
className="my-8 size-fit"
label={t('common_add')}
variant={ODS_BUTTON_VARIANT.outline}
onClick={openAddContactModal}
isDisabled={discoveryProject}
/>
)}
{isAdmin && (
<OdsButton
className="my-8 size-fit"
label={t('common_add')}
variant={ODS_BUTTON_VARIANT.outline}
onClick={openAddContactModal}
isDisabled={discoveryProject}
/>
)}

<Datagrid
columns={columns}
items={flattenData as AccountAcl[]}
totalItems={userAcls.length}
hasNextPage={hasNextPage}
onFetchNextPage={fetchNextPage}
/>
</div>

<Outlet />
</BaseLayout>
<Datagrid
columns={columns}
items={flattenData as AccountAcl[]}
totalItems={userAcls.length}
hasNextPage={hasNextPage}
onFetchNextPage={fetchNextPage}
/>
</div>
<Outlet />
</BaseLayout>
</RedirectionGuard>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,20 +105,6 @@ describe('AddAccountAcl', () => {
mockUseParams.mockReturnValue({ projectId: 'p-1' } as any);
});

it('shows NIC handle label for non-US/CA users and email for US/CA', () => {
mockUseAdd.mockReturnValue({
addAccountAclToProject: vi.fn(),
isPending: false,
} as any);
const wrapperFR = createWrapper(baseContextFR);
render(<AddAccountAcl />, { wrapper: wrapperFR });
expect(screen.getByText('cpb_rights_table_nichandle')).toBeInTheDocument();

const wrapperUS = createWrapper(baseContextUS);
render(<AddAccountAcl />, { wrapper: wrapperUS });
expect(screen.getByText('cpb_rights_table_email')).toBeInTheDocument();
});

it('trims and normalize the NIC submitted to API call', () => {
const addSpy = vi.fn();
mockUseAdd.mockReturnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export default function AddAccountAcl() {
const [accountRights, setAccountRights] = useState<AclRight>('readOnly');
const [isAccountInputTouched, setIsAccountInputTouched] = useState(false);
const handleClose = () => navigate('..');
const isUSorCA = ['US', 'CA'].includes(user.country.toUpperCase());
const normalizedAccountId = normalizeAccountId(accountToAdd);

const { addAccountAclToProject, isPending } = useAddAccountAclToProject({
Expand Down Expand Up @@ -76,9 +75,7 @@ export default function AddAccountAcl() {
}
>
<label htmlFor="contactInput" slot="label">
{isUSorCA
? t('cpb_rights_table_email')
: t('cpb_rights_table_nichandle')}
{t('cpb_rights_table_nichandle')}
</label>
<OdsInput
type={ODS_INPUT_TYPE.text}
Expand Down
85 changes: 49 additions & 36 deletions packages/manager/apps/pci-project/src/pages/home/Header.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import {
useProject,
usePciUrl,
} from '@ovh-ux/manager-pci-common';
import { BaseLayout, ChangelogButton } from '@ovh-ux/manager-react-components';
import {
BaseLayout,
ChangelogButton,
RedirectionGuard,
} from '@ovh-ux/manager-react-components';
import { ODS_BADGE_COLOR, ODS_BADGE_SIZE } from '@ovhcloud/ods-components';
import {
OdsBreadcrumb,
Expand All @@ -18,51 +22,60 @@ import {
import { ROADMAP_CHANGELOG_LINKS } from '@/constants';
import { useProjectTabs } from '@/hooks/useProjectTabs';
import QuotaAlert from './components/QuotaAlert.component';
import { urls } from '@/routes/routes.constant';

export default function ProjectHeader() {
const { t } = useTranslation('project');

const hrefProject = usePciUrl();
const { data: project, isLoading, error } = useProject();
const {
data: project,
isLoading: isProjectLoading,
isError: isProjectError,
} = useProject();
const tabs = useProjectTabs();

const isDiscovery = isDiscoveryProject(project);

if (error) throw error;

return (
<BaseLayout
breadcrumb={
isLoading ? (
<OdsSkeleton className="w-48 h-6" />
) : (
<OdsBreadcrumb>
<OdsBreadcrumbItem
href={hrefProject}
label={project?.description}
/>
</OdsBreadcrumb>
)
}
header={{
title: project?.description,
badge: isDiscovery
? {
color: ODS_BADGE_COLOR.information,
size: ODS_BADGE_SIZE.md,
label: t('pci_projects_project_label_discovery'),
}
: undefined,
changelogButton: <ChangelogButton links={ROADMAP_CHANGELOG_LINKS} />,
}}
tabs={
<nav aria-label={t('pci_projects_project_main_navigation')}>
<TabsPanel tabs={tabs} />
</nav>
}
<RedirectionGuard
condition={isProjectError}
isLoading={isProjectLoading}
route={urls.root}
>
<QuotaAlert />
<Outlet />
</BaseLayout>
<BaseLayout
breadcrumb={
isProjectLoading ? (
<OdsSkeleton className="w-48 h-6" />
) : (
<OdsBreadcrumb>
<OdsBreadcrumbItem
href={hrefProject}
label={project?.description}
/>
</OdsBreadcrumb>
)
}
header={{
title: project?.description,
badge: isDiscovery
? {
color: ODS_BADGE_COLOR.information,
size: ODS_BADGE_SIZE.md,
label: t('pci_projects_project_label_discovery'),
}
: undefined,
changelogButton: <ChangelogButton links={ROADMAP_CHANGELOG_LINKS} />,
}}
tabs={
<nav aria-label={t('pci_projects_project_main_navigation')}>
<TabsPanel tabs={tabs} />
</nav>
}
>
<QuotaAlert />
<Outlet />
</BaseLayout>
</RedirectionGuard>
);
}
Loading