Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
f41f0c6
feat(billing): merge credit cards and billing details into unified se…
jul-dan Jan 30, 2026
5a2ba56
feat(billing): integrate credit card form into billing details
jul-dan Jan 30, 2026
23eb1bf
refactor(billing): move credit card fields to credit cards section
jul-dan Jan 30, 2026
05a9859
fix(billing): unify form state for credit card and billing info
jul-dan Jan 30, 2026
a325ce1
feat(billing): make additional fields required and improve validation UX
jul-dan Jan 30, 2026
255fce2
feat(billing): make country field required
jul-dan Jan 30, 2026
8b7a224
fix(billing): save credit card only after billing info validation suc…
jul-dan Jan 30, 2026
07d9991
feat(billing): add edit button for credit cards and improve UX
jul-dan Jan 30, 2026
025b7c1
fix(billing): implement card replacement on edit and update tests
jul-dan Jan 30, 2026
90462a9
refactor(billing): clean up code and improve UX
jul-dan Jan 30, 2026
baa157e
style(billing): change add card button to outline variant
jul-dan Jan 30, 2026
b4d9b67
chore(billing): apply linter formatting
jul-dan Jan 30, 2026
3fac36a
fix(billing): use inline type imports as per project standards
jul-dan Jan 30, 2026
032c961
refactor(billing): remove comments and optimize useEffect usage
jul-dan Jan 30, 2026
a2619a0
refactor(billing): remove all useEffect hooks
jul-dan Jan 30, 2026
4df40f0
fix(billing): improve scroll behavior when adding card
jul-dan Jan 30, 2026
7c039b1
fix(billing): remove automatic scroll when adding card
jul-dan Jan 30, 2026
7c7381d
refactor(billing): remove useless BillingDetailsFeature wrapper
jul-dan Jan 30, 2026
d713de6
fix(billing): prevent field width changes when displaying errors
jul-dan Feb 4, 2026
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

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,30 +1,117 @@
import { type CreditCard } from 'qovery-typescript-axios'
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 BillingInfoRequest, type CreditCard } from 'qovery-typescript-axios'
import { useRef, useState } from 'react'
import { FormProvider, useForm } from 'react-hook-form'
import { useParams } from 'react-router-dom'
import { useCreditCards, useDeleteCreditCard } from '@qovery/domains/organizations/feature'
import { AddCreditCardModalFeature, type CreditCardFormValues } from '@qovery/shared/console-shared'
import { useModal, useModalConfirmation } from '@qovery/shared/ui'
import {
useAddCreditCard,
useBillingInfo,
useCreditCards,
useDeleteCreditCard,
useEditBillingInfo,
} from '@qovery/domains/organizations/feature'
import { countries } from '@qovery/shared/enums'
import { type Value } from '@qovery/shared/interfaces'
import { IconFlag, toastError, useModalConfirmation } from '@qovery/shared/ui'
import { useDocumentTitle } from '@qovery/shared/util-hooks'
import { loadChargebee } from '@qovery/shared/util-payment'
import { type SerializedError } from '@qovery/shared/utils'
import PageOrganizationBilling from '../../ui/page-organization-billing/page-organization-billing'

export function PageOrganizationBillingFeature() {
useDocumentTitle('Billing details - Organization settings')
const { organizationId = '' } = useParams()
const { openModal } = useModal()
const { openModalConfirmation } = useModalConfirmation()
const { data: creditCards = [], isLoading: isLoadingCreditCards } = useCreditCards({ organizationId })
const { mutateAsync: deleteCreditCard } = useDeleteCreditCard()
const { data: billingInfo, isLoading: isLoadingBillingInfo } = useBillingInfo({ organizationId })
const { mutateAsync: editBillingInfo } = useEditBillingInfo()
const { mutateAsync: addCreditCard } = useAddCreditCard()

const methods = useForm<CreditCardFormValues>({
const [showAddCard, setShowAddCard] = useState(false)
const [editInProcess, setEditInProcess] = useState(false)
const [cbInstance, setCbInstance] = useState<CbInstance | null>(null)
const [isCardReady, setIsCardReady] = useState(false)
const [editingCardId, setEditingCardId] = useState<string | null>(null)
const cardRef = useRef<FieldContainer>(null)

const countryValues = countries.map((country) => ({
label: country.name,
value: country.code,
icon: <IconFlag code={country.code} />,
}))

const methods = useForm<BillingInfoRequest>({
mode: 'onChange',
values: billingInfo as BillingInfoRequest,
})

const openNewCreditCardModal = () => {
openModal({
content: <AddCreditCardModalFeature organizationId={organizationId} />,
})
const handleAddCard = async (cardId?: string) => {
setShowAddCard(true)
setEditingCardId(cardId || null)

try {
const instance = await loadChargebee()
setCbInstance(instance)
} catch (error) {
return
}
}

const handleCancelAddCard = () => {
setShowAddCard(false)
setIsCardReady(false)
setCbInstance(null)
setEditingCardId(null)
}

const onSubmit = methods.handleSubmit(async (data) => {
if (!organizationId) return

setEditInProcess(true)

try {
const response = await editBillingInfo({
organizationId,
billingInfoRequest: data,
})
methods.reset(response as BillingInfoRequest)

if (showAddCard && isCardReady && cardRef.current) {
const tokenData = await cardRef.current.tokenize({})

if (!tokenData.token) {
throw new Error('No token returned from Chargebee')
}

await addCreditCard({
organizationId,
creditCardRequest: {
token: tokenData.token,
cvv: '',
number: `****${tokenData.card?.last4 || ''}`,
expiry_year: tokenData.card?.expiry_year || 0,
expiry_month: tokenData.card?.expiry_month || 0,
},
})

if (editingCardId) {
await deleteCreditCard({ organizationId, creditCardId: editingCardId })
}

setShowAddCard(false)
setIsCardReady(false)
setCbInstance(null)
setEditingCardId(null)
}
} catch (error) {
toastError(error as unknown as SerializedError)
} finally {
setEditInProcess(false)
}
})

const onDeleteCreditCard = (creditCard: CreditCard) => {
openModalConfirmation({
title: 'Delete credit card',
Expand All @@ -40,9 +127,18 @@ export function PageOrganizationBillingFeature() {
<FormProvider {...methods}>
<PageOrganizationBilling
creditCards={creditCards}
openNewCreditCardModal={openNewCreditCardModal}
onAddCard={handleAddCard}
onDeleteCard={onDeleteCreditCard}
creditCardLoading={isLoadingCreditCards}
showAddCard={showAddCard}
onCancelAddCard={handleCancelAddCard}
cbInstance={cbInstance}
cardRef={cardRef}
onCardReady={() => setIsCardReady(true)}
countryValues={countryValues}
loadingBillingInfos={isLoadingBillingInfo}
editInProcess={editInProcess}
onSubmit={onSubmit}
/>
</FormProvider>
)
Expand Down
Loading