+
(
)}
/>
(
)}
/>
@@ -139,14 +151,16 @@ export function BillingDetails(props: BillingDetailsProps) {
(
)}
/>
@@ -169,7 +183,6 @@ export function BillingDetails(props: BillingDetailsProps) {
data-testid="submit-button"
type="submit"
size="lg"
- disabled={!formState.isValid}
loading={props.editInProcess}
onClick={props.onSubmit as () => void}
>
@@ -178,7 +191,7 @@ export function BillingDetails(props: BillingDetailsProps) {
>
)}
-
+ >
)
}
diff --git a/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.spec.tsx b/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.spec.tsx
index 966c9b6f7e3..e65ad306a92 100644
--- a/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.spec.tsx
+++ b/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.spec.tsx
@@ -1,35 +1,86 @@
import { getAllByTestId, getByTestId, render } from '__tests__/utils/setup-jest'
+import { wrapWithReactHookForm } from '__tests__/utils/wrap-with-react-hook-form'
+import { type BillingInfoRequest } from 'qovery-typescript-axios'
import { creditCardsFactoryMock } from '@qovery/shared/factories'
import PageOrganizationBilling, { type PageOrganizationBillingProps } from './page-organization-billing'
-const mockOpenNewCreditCardModal = jest.fn()
+const mockOnAddCard = jest.fn()
const mockDeleteCard = jest.fn()
+const mockOnCancelAddCard = jest.fn()
+const mockOnSubmit = jest.fn()
+
+const defaultBillingValues: BillingInfoRequest = {
+ first_name: 'John',
+ last_name: 'Doe',
+ company: 'Qovery',
+ address: '1 rue de la paix',
+ city: 'Paris',
+ state: 'Ile de France',
+ zip: '75000',
+ country_code: 'FR',
+ vat_number: 'FR123456789',
+ email: 'test@qovery.com',
+}
+
const props: PageOrganizationBillingProps = {
creditCards: creditCardsFactoryMock(3),
- openNewCreditCardModal: mockOpenNewCreditCardModal,
+ onAddCard: mockOnAddCard,
onDeleteCard: mockDeleteCard,
creditCardLoading: false,
+ showAddCard: false,
+ onCancelAddCard: mockOnCancelAddCard,
+ cbInstance: null,
+ countryValues: [
+ { label: 'France', value: 'FR' },
+ { label: 'United States', value: 'US' },
+ ],
+ loadingBillingInfos: false,
+ editInProcess: false,
+ onSubmit: mockOnSubmit,
}
describe('PageOrganizationBilling', () => {
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
+
it('should render successfully', () => {
- const { baseElement } = render(
)
+ const { baseElement } = render(
+ wrapWithReactHookForm
(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
expect(baseElement).toBeTruthy()
})
it('should have 3 credit card rows', () => {
- const { baseElement } = render()
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
expect(getAllByTestId(baseElement, 'credit-card-row')).toHaveLength(3)
})
- it('should display 2 spinners if loading and no card in the store', () => {
- const { baseElement } = render()
- // 1 for the for the credit card and one for the billing form
- expect(getAllByTestId(baseElement, 'spinner')).toHaveLength(2)
+ it('should display 1 spinner if loading and no card in the store', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(
+ ,
+ {
+ defaultValues: defaultBillingValues,
+ }
+ )
+ )
+ // 1 for the credit card section
+ expect(getAllByTestId(baseElement, 'spinner')).toHaveLength(1)
})
- it('should call deleteCard methods', () => {
- const { baseElement } = render()
+ it('should call deleteCard method', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
const deleteButton = getAllByTestId(baseElement, 'delete-credit-card')[0]
deleteButton.click()
@@ -37,12 +88,51 @@ describe('PageOrganizationBilling', () => {
expect(mockDeleteCard).toHaveBeenCalledWith(props.creditCards[0])
})
- it('should open the creation modal', () => {
- const { baseElement } = render()
+ it('should call onAddCard when clicking add new card button', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
const addButton = getByTestId(baseElement, 'add-new-card-button')
addButton.click()
- expect(mockOpenNewCreditCardModal).toHaveBeenCalled()
+ expect(mockOnAddCard).toHaveBeenCalledWith()
+ })
+
+ it('should call onAddCard with card id when clicking edit button', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
+ const editButton = getAllByTestId(baseElement, 'edit-credit-card')[0]
+
+ editButton.click()
+
+ expect(mockOnAddCard).toHaveBeenCalledWith(props.creditCards[0].id)
+ })
+
+ it('should not display add new card button when cards exist', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
+ const addButton = baseElement.querySelector('[data-testid="add-new-card-button"]')
+
+ expect(addButton).not.toBeInTheDocument()
+ })
+
+ it('should display empty state when no cards and not showing add card form', () => {
+ const { baseElement } = render(
+ wrapWithReactHookForm(, {
+ defaultValues: defaultBillingValues,
+ })
+ )
+ const emptyState = getByTestId(baseElement, 'placeholder-credit-card')
+
+ expect(emptyState).toBeInTheDocument()
})
})
diff --git a/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.tsx b/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.tsx
index e015e0028b9..c794a25285a 100644
--- a/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.tsx
+++ b/libs/pages/settings/src/lib/ui/page-organization-billing/page-organization-billing.tsx
@@ -1,4 +1,9 @@
+import { CardCVV, CardComponent, CardExpiry, CardNumber, Provider } from '@chargebee/chargebee-js-react-wrapper'
+import { type default as FieldContainer } from '@chargebee/chargebee-js-react-wrapper/dist/components/FieldContainer'
+import { type default as CbInstance } from '@chargebee/chargebee-js-types/cb-types/models/cb-instance'
import { type CreditCard } from 'qovery-typescript-axios'
+import { type FormEventHandler, type RefObject } from 'react'
+import { type Value } from '@qovery/shared/interfaces'
import {
BlockContent,
Button,
@@ -9,82 +14,169 @@ import {
LoaderSpinner,
Section,
} from '@qovery/shared/ui'
-import BillingDetailsFeature from '../../feature/page-organization-billing-feature/billing-details-feature/billing-details-feature'
+import { fieldStyles } from '@qovery/shared/util-payment'
+import BillingDetails from './billing-details/billing-details'
export interface PageOrganizationBillingProps {
creditCards: CreditCard[]
- openNewCreditCardModal: () => void
+ onAddCard: (cardId?: string) => void
onDeleteCard: (creditCard: CreditCard) => void
creditCardLoading?: boolean
+ showAddCard?: boolean
+ onCancelAddCard?: () => void
+ cbInstance?: CbInstance | null
+ cardRef?: RefObject
+ onCardReady?: () => void
+ countryValues?: Value[]
+ loadingBillingInfos?: boolean
+ editInProcess?: boolean
+ onSubmit?: FormEventHandler
}
export function PageOrganizationBilling(props: PageOrganizationBillingProps) {
return (
-
- Payment method
-
-
+ Payment method
-
- {props.creditCardLoading && props.creditCards.length === 0 ? (
-
-
+
+
+
+
Credit card
- ) : props.creditCards.length > 0 ? (
-
-
- If you want to modify the card, delete the existing one and add a new.
-
- {props.creditCards.map((creditCard) => (
-
-
-
-
-
- ))}
-
- ) : (
-
-
-
- No credit cards found.
Please add one.
-
-
- )}
-
-
+ {props.creditCardLoading && props.creditCards.length === 0 && !props.showAddCard ? (
+
+
+
+ ) : (
+ <>
+ {props.creditCards.length > 0 && !props.showAddCard && (
+
+ {props.creditCards.map((creditCard) => (
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ {props.showAddCard && (
+ <>
+
+
Add credit card
+
+
+
+ {!props.cbInstance ? (
+
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+ )}
+
+ {!props.showAddCard && props.creditCards.length === 0 && (
+
+
+
+ No credit card found.
Please add one.
+
+
+
+
+
+ )}
+ >
+ )}
+
+
+
+
+
+
)