-
{plan}
+
{plan}
{isCurrentPlan(plan) && (
-
+
Current
)}
@@ -65,7 +70,7 @@ export function PlanSelectionModal(props: PlanSelectionModalProps) {
))}
- {error && {error.message}
}
+ {error && {error.message}
}
)}
/>
@@ -88,4 +93,63 @@ export function PlanSelectionModal(props: PlanSelectionModalProps) {
)
}
-export default PlanSelectionModal
+export interface PlanSelectionModalFeatureProps {
+ organizationId?: string
+ closeModal: () => void
+ currentPlan?: string
+}
+
+/**
+ * Normalizes the current plan to match the 2025 plan enum values
+ * Only returns a value if the current plan is already a 2025 plan
+ * Returns undefined for legacy plans to avoid pre-selection
+ */
+function normalizePlanSelection(currentPlan?: string): PlanEnum | undefined {
+ if (!is2025Plan(currentPlan)) {
+ return undefined
+ }
+
+ return match(currentPlan?.toUpperCase())
+ .with(P.string.includes('USER'), () => PlanEnum.USER_2025)
+ .with(P.string.includes('TEAM'), () => PlanEnum.TEAM_2025)
+ .with(P.string.includes('BUSINESS'), () => PlanEnum.BUSINESS_2025)
+ .with(P.string.includes('ENTERPRISE'), () => PlanEnum.ENTERPRISE_2025)
+ .otherwise(() => undefined)
+}
+
+export function PlanSelectionModalFeature({ organizationId, closeModal, currentPlan }: PlanSelectionModalFeatureProps) {
+ const normalizedPlan = normalizePlanSelection(currentPlan)
+
+ const methods = useForm<{ plan: PlanEnum }>({ defaultValues: { plan: normalizedPlan as PlanEnum }, mode: 'all' })
+ const [isSubmitting, setIsSubmitting] = useState(false)
+ const { mutateAsync: changePlan } = useChangePlan()
+
+ const onSubmit = methods.handleSubmit(async (data) => {
+ if (organizationId && data.plan) {
+ setIsSubmitting(true)
+
+ try {
+ await changePlan({ organizationId, plan: data.plan })
+ closeModal()
+ } catch (error) {
+ console.error(error)
+ }
+
+ setIsSubmitting(false)
+ }
+ })
+
+ return (
+
+
+
+ )
+}
+
+export default PlanSelectionModalFeature
diff --git a/libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.spec.tsx b/libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.spec.tsx
new file mode 100644
index 00000000000..284e8aced8f
--- /dev/null
+++ b/libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.spec.tsx
@@ -0,0 +1,75 @@
+import { wrapWithReactHookForm } from '__tests__/utils/wrap-with-react-hook-form'
+import { renderWithProviders, screen } from '@qovery/shared/util-tests'
+import * as addCreditCodeHooks from '../../hooks/use-add-credit-code/use-add-credit-code'
+import PromoCodeModalFeature, {
+ PromoCodeModal,
+ type PromoCodeModalProps,
+ type PromocodeModalFeatureProps,
+} from './promo-code-modal-feature'
+
+const useAddCreditCodeSpy = jest.spyOn(addCreditCodeHooks, 'useAddCreditCode')
+
+const featureProps: PromocodeModalFeatureProps = {
+ closeModal: jest.fn(),
+ organizationId: '1',
+}
+
+const modalProps: PromoCodeModalProps = {
+ isSubmitting: false,
+ onClose: jest.fn(),
+ onSubmit: jest.fn((event) => event.preventDefault()),
+}
+
+describe('PromoCodeModal', () => {
+ it('should render successfully', () => {
+ const { baseElement } = renderWithProviders(wrapWithReactHookForm(
))
+ expect(baseElement).toBeTruthy()
+ })
+
+ it('should show spinner when submitting', () => {
+ renderWithProviders(wrapWithReactHookForm(
))
+ screen.getByTestId('spinner')
+ })
+
+ it('should call onSubmit when clicking submit', async () => {
+ const onSubmit = jest.fn((event) => event.preventDefault())
+
+ const { userEvent } = renderWithProviders(
+ wrapWithReactHookForm(
, {
+ defaultValues: { code: 'test' },
+ })
+ )
+
+ await userEvent.click(screen.getByTestId('submit-button'))
+
+ expect(onSubmit).toHaveBeenCalled()
+ })
+})
+
+describe('PromoCodeModalFeature', () => {
+ const mutateAsyncMock = jest.fn()
+
+ beforeEach(() => {
+ mutateAsyncMock.mockReset()
+ useAddCreditCodeSpy.mockReturnValue({
+ mutateAsync: mutateAsyncMock,
+ })
+ })
+
+ it('should render successfully', () => {
+ const { baseElement } = renderWithProviders(
)
+ expect(baseElement).toBeTruthy()
+ })
+
+ it('should useAddCreditCode with good params', async () => {
+ const { userEvent } = renderWithProviders(
)
+
+ const input = screen.getByLabelText('Promo code')
+ await userEvent.type(input, 'test')
+
+ const button = screen.getByTestId('submit-button')
+ await userEvent.click(button)
+
+ expect(mutateAsyncMock).toHaveBeenCalledWith({ organizationId: '1', code: 'test' })
+ })
+})
diff --git a/libs/pages/settings/src/lib/ui/page-organization-billing-summary/promo-code-modal/promo-code-modal.tsx b/libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.tsx
similarity index 53%
rename from libs/pages/settings/src/lib/ui/page-organization-billing-summary/promo-code-modal/promo-code-modal.tsx
rename to libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.tsx
index cdb7cf66ddb..6563393467c 100644
--- a/libs/pages/settings/src/lib/ui/page-organization-billing-summary/promo-code-modal/promo-code-modal.tsx
+++ b/libs/domains/organizations/feature/src/lib/settings-billing-summary/promo-code-modal-feature/promo-code-modal-feature.tsx
@@ -1,6 +1,9 @@
+import { useState } from 'react'
import { type FormEventHandler } from 'react'
+import { FormProvider, useForm } from 'react-hook-form'
import { Controller, useFormContext } from 'react-hook-form'
import { Button, InputText } from '@qovery/shared/ui'
+import { useAddCreditCode } from '../../hooks/use-add-credit-code/use-add-credit-code'
export interface PromoCodeModalProps {
onClose: () => void
@@ -13,7 +16,7 @@ export function PromoCodeModal(props: PromoCodeModalProps) {
return (
-
Promo code
+
Promo code
diff --git a/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap b/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
index 6a91f3a3846..cdf7f1f2dd4 100644
--- a/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
+++ b/libs/pages/application/src/lib/ui/page-settings-resources/__snapshots__/page-settings-resources.spec.tsx.snap
@@ -38,6 +38,7 @@ exports[`PageSettingsResources should render warning box and icon for cpu 1`] =
Need help here?
+