3. Open the embedded Azure shell and run the following command
@@ -782,7 +752,7 @@ bash -s -- $GOOGLE_CLOUD_PROJECT qovery_role qovery-service-account"
Please note that this script can take up to 30 seconds to complete.
-
+
$
{snippet}
diff --git a/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx b/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx
index 9ce32d52478..cbb546167da 100644
--- a/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx
+++ b/libs/domains/clusters/feature/src/lib/credentials-list-clusters-modal/credentials-list-clusters-modal.tsx
@@ -1,8 +1,7 @@
import * as Dialog from '@radix-ui/react-dialog'
import { type ClusterCredentials, type CredentialCluster } from 'qovery-typescript-axios'
-import { Link } from 'react-router-dom'
import { CLUSTER_SETTINGS_CREDENTIALS_URL, CLUSTER_SETTINGS_URL, CLUSTER_URL } from '@qovery/shared/routes'
-import { Heading, Section } from '@qovery/shared/ui'
+import { Heading, Link, Section } from '@qovery/shared/ui'
import { pluralize } from '@qovery/shared/util-js'
import { ClusterAvatar } from '../cluster-avatar/cluster-avatar'
@@ -21,20 +20,23 @@ export function CredentialsListClustersModal({
return (
-
+
Attached {pluralize(clusters.length, 'cluster', 'clusters')} ({clusters.length})
- Credential: {credential.name}
-
+
+ Credential: {credential.name}
+
+
{clusters.map((cluster) => (
-
+
{cluster.name}
diff --git a/libs/domains/organizations/feature/src/index.ts b/libs/domains/organizations/feature/src/index.ts
index 22aa22f8ebb..192c25fa35b 100644
--- a/libs/domains/organizations/feature/src/index.ts
+++ b/libs/domains/organizations/feature/src/index.ts
@@ -90,6 +90,7 @@ export * from './lib/invoice-banner/invoice-banner'
export * from './lib/free-trial-banner/free-trial-banner'
export * from './lib/settings-general/settings-general'
export * from './lib/settings-labels-annotations/settings-labels-annotations'
+export * from './lib/settings-cloud-credentials/settings-cloud-credentials'
export * from './lib/settings-git-repository-access/settings-git-repository-access'
export * from './lib/settings-helm-repositories/settings-helm-repositories'
export * from './lib/settings-container-registries/settings-container-registries'
diff --git a/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx
new file mode 100644
index 00000000000..f2c5a9016a6
--- /dev/null
+++ b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.spec.tsx
@@ -0,0 +1,108 @@
+import { type OrganizationCrendentialsResponseListResultsInner } from 'qovery-typescript-axios'
+import { useDeleteCloudProviderCredential } from '@qovery/domains/cloud-providers/feature'
+import { renderWithProviders, screen } from '@qovery/shared/util-tests'
+import { useOrganizationCredentials } from '../hooks/use-organization-credentials/use-organization-credentials'
+import { SettingsCloudCredentials } from './settings-cloud-credentials'
+
+jest.mock('../hooks/use-organization-credentials/use-organization-credentials')
+jest.mock('@qovery/domains/cloud-providers/feature', () => ({
+ ...jest.requireActual('@qovery/domains/cloud-providers/feature'),
+ useDeleteCloudProviderCredential: jest.fn(),
+}))
+jest.mock('@tanstack/react-router', () => ({
+ ...jest.requireActual('@tanstack/react-router'),
+ useParams: () => ({ organizationId: 'org-1' }),
+}))
+
+describe('SettingsCloudCredentials', () => {
+ const useOrganizationCredentialsMock = useOrganizationCredentials as jest.MockedFunction<
+ typeof useOrganizationCredentials
+ >
+ const useDeleteCloudProviderCredentialMock = useDeleteCloudProviderCredential as jest.MockedFunction<
+ typeof useDeleteCloudProviderCredential
+ >
+
+ let mockCredentials: OrganizationCrendentialsResponseListResultsInner[] = []
+
+ beforeEach(() => {
+ mockCredentials = []
+ useOrganizationCredentialsMock.mockReturnValue({
+ data: mockCredentials,
+ } as ReturnType)
+ useDeleteCloudProviderCredentialMock.mockReturnValue({
+ mutate: jest.fn(),
+ } as ReturnType)
+ })
+
+ it('should render', () => {
+ const { baseElement } = renderWithProviders()
+ expect(baseElement).toBeTruthy()
+ })
+
+ it('should render the content wrapper within the max-width container', () => {
+ const { container } = renderWithProviders()
+
+ expect(container.querySelector('.max-w-content-with-navigation-left')).toBeInTheDocument()
+ })
+
+ it('should render an empty state when there are no credentials', () => {
+ renderWithProviders()
+
+ screen.getByText('All credentials related to your clusters will appear here after creation.')
+ })
+
+ it('should render used and unused credentials with actions', () => {
+ mockCredentials = [
+ {
+ credential: {
+ id: '1',
+ name: 'Credential 1',
+ object_type: 'AWS',
+ access_key_id: '123',
+ },
+ clusters: [
+ {
+ id: '1',
+ name: 'Cluster 1',
+ },
+ ],
+ },
+ {
+ credential: {
+ id: '2',
+ name: 'Credential 2',
+ object_type: 'GCP',
+ },
+ clusters: [],
+ },
+ ]
+ useOrganizationCredentialsMock.mockReturnValue({
+ data: mockCredentials,
+ } as ReturnType)
+
+ renderWithProviders()
+
+ screen.getByText('Configured credentials')
+ screen.getByText('Unused credentials')
+ screen.getByText('Credential 1')
+ screen.getByText('Credential 2')
+
+ expect(screen.getAllByTestId('view-credential')).toHaveLength(1)
+ expect(screen.getAllByTestId('delete-credential')).toHaveLength(1)
+ expect(screen.getByTestId('delete-credential')).toBeEnabled()
+ })
+
+ it('should show the cloud provider options in the create menu', async () => {
+ const { userEvent } = renderWithProviders()
+
+ await userEvent.click(screen.getByText('New credential'))
+
+ expect(await screen.findByText('AWS')).toBeInTheDocument()
+ expect(screen.getByText('GCP')).toBeInTheDocument()
+ expect(screen.getByText('Azure')).toBeInTheDocument()
+ expect(screen.getByText('Scaleway')).toBeInTheDocument()
+
+ const awsItem = screen.getByText('AWS').closest('[role="menuitem"]')
+ expect(awsItem).toHaveClass('text-neutral')
+ })
+})
diff --git a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx
similarity index 72%
rename from libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx
rename to libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx
index 6518ace7e9e..7ddb29b2f8e 100644
--- a/libs/pages/settings/src/lib/feature/page-organization-credentials-feature/page-organization-credentials-feature.tsx
+++ b/libs/domains/organizations/feature/src/lib/settings-cloud-credentials/settings-cloud-credentials.tsx
@@ -1,16 +1,16 @@
import { useQueryClient } from '@tanstack/react-query'
+import { useParams } from '@tanstack/react-router'
import { CloudProviderEnum, type ClusterCredentials, type CredentialCluster } from 'qovery-typescript-axios'
import { Suspense, useMemo, useState } from 'react'
-import { useParams } from 'react-router-dom'
import { match } from 'ts-pattern'
import { useDeleteCloudProviderCredential } from '@qovery/domains/cloud-providers/feature'
import { ClusterAvatar, ClusterCredentialsModal, CredentialsListClustersModal } from '@qovery/domains/clusters/feature'
-import { useOrganizationCredentials } from '@qovery/domains/organizations/feature'
-import { NeedHelp } from '@qovery/shared/assistant/feature'
-import { BlockContent, Heading, Section, Skeleton } from '@qovery/shared/ui'
+import { SettingsHeading } from '@qovery/shared/console-shared'
+import { BlockContent, Section, Skeleton } from '@qovery/shared/ui'
import { Button, DropdownMenu, Icon, Indicator, useModal, useModalConfirmation } from '@qovery/shared/ui'
import { useDocumentTitle } from '@qovery/shared/util-hooks'
import { queries } from '@qovery/state/util-queries'
+import { useOrganizationCredentials } from '../hooks/use-organization-credentials/use-organization-credentials'
const convertToCloudProviderEnum = (cloudProvider: ClusterCredentials['object_type']): CloudProviderEnum => {
return match(cloudProvider)
@@ -34,7 +34,7 @@ type CredentialRowProps = {
const CredentialRow = ({ credential, clusters, onEdit, onOpen, onDelete }: CredentialRowProps) => {
return (