diff --git a/ditto/base.json b/ditto/base.json index 4d46e2fc1..07e6cbf1b 100644 --- a/ditto/base.json +++ b/ditto/base.json @@ -760,6 +760,7 @@ "text_6657cdd8cea6bf010e1ce128": "Fixed top-up", "text_66584178ee91f801012606a6": "Target ongoing balance should be higher than threshold", "text_66584178ee91f801012606ac": "Threshold should be lower than target ongoing balance", + "text_66599bfb69fba1010535c5c2": "Start on", "text_64aeb7b998c4322918c84204": "Payment methods", "text_64aeb7b998c4322918c84208": "Card", "text_64aeb7b998c4322918c8420c": "SEPA Direct Debit", diff --git a/src/components/wallets/WalletCodeSnippet.tsx b/src/components/wallets/WalletCodeSnippet.tsx index ee9a65b3e..d1f970222 100644 --- a/src/components/wallets/WalletCodeSnippet.tsx +++ b/src/components/wallets/WalletCodeSnippet.tsx @@ -58,7 +58,7 @@ curl --location --request ${isEdition ? 'PUT' : 'POST'} "${apiUrl}/api/v1/wallet "lago_id": "${recurringTransactionRules[0].lagoId}",` : '' } - "method": "${wallet.recurringTransactionRules?.[0].method || '__MUST_BE_DEFINED__'}"${ + "method": "${wallet.recurringTransactionRules?.[0].method || '__MUST_BE_DEFINED__'}",${ wallet.recurringTransactionRules?.[0].method === RecurringTransactionMethodEnum.Fixed ? ` "paid_credits": "${ @@ -83,7 +83,7 @@ curl --location --request ${isEdition ? 'PUT' : 'POST'} "${apiUrl}/api/v1/wallet ? ` "target_ongoing_balance": "${ wallet.recurringTransactionRules?.[0].targetOngoingBalance || '0' - }"` + }",` : '' } "trigger": "${wallet.recurringTransactionRules?.[0].trigger || '__MUST_BE_DEFINED__'}"${ @@ -92,7 +92,7 @@ curl --location --request ${isEdition ? 'PUT' : 'POST'} "${apiUrl}/api/v1/wallet ? `, "interval": "${ wallet.recurringTransactionRules?.[0].interval || '__MUST_BE_DEFINED__' - }"` + }",` : '' }${ wallet.recurringTransactionRules?.[0].trigger === @@ -100,7 +100,15 @@ curl --location --request ${isEdition ? 'PUT' : 'POST'} "${apiUrl}/api/v1/wallet ? `, "threshold_credits": "${wallet.recurringTransactionRules?.[0].thresholdCredits || '0'}"` : '' + }${ + wallet.recurringTransactionRules?.[0].trigger === + RecurringTransactionTriggerEnum.Interval && + wallet.recurringTransactionRules?.[0].startedAt + ? ` + "started_at": "${wallet.recurringTransactionRules?.[0].startedAt}"` + : '' } + } ]` : '' } diff --git a/src/components/wallets/__tests__/utils.test.ts b/src/components/wallets/__tests__/utils.test.ts index 19186dc49..cd2de2c91 100644 --- a/src/components/wallets/__tests__/utils.test.ts +++ b/src/components/wallets/__tests__/utils.test.ts @@ -15,13 +15,6 @@ import { } from '~/generated/graphql' import { TWalletDataForm } from '~/pages/WalletForm/types' -const formatOptions: Intl.DateTimeFormatOptions = { - weekday: undefined, - year: 'numeric', - month: 'long', - day: 'numeric', -} - describe('Wallet Utils', () => { describe('toNumber', () => { it('should return 0 when the value is undefined', () => { @@ -60,64 +53,94 @@ describe('Wallet Utils', () => { it('should return the date reference for the customer timezone in French', () => { expect( - getDateRef(TimezoneEnum.TzEuropeParis, 'fr').set({ year: 2024, month: 5, day: 28 }) - .weekdayLong, + getDateRef(TimezoneEnum.TzEuropeParis, 'fr').set({ + year: 2024, + month: 5, + day: 28, + }).weekdayLong, ).toBe('mardi') }) }) describe('getNextRecurringDate', () => { - it('should return the next weekly date', () => { - const date = new Date() + beforeEach(() => { + const expectedNow = DateTime.local(2024, 5, 5) + + Settings.now = () => expectedNow.toMillis() + }) + it('Weekly - should return May 12nd when current date is May 5th', () => { expect( getNextRecurringDate({ timezone: TimezoneEnum.TzEuropeParis, interval: RecurringTransactionIntervalEnum.Weekly, }), - ).toBe(new Date(date.setDate(date.getDate() + 7)).toLocaleDateString('en-US', formatOptions)) + ).toBe('May 12, 2024') }) - it('should return the next monthly date', () => { - const date = new Date() + it('Weekly - should return June 4th when current date is May 28th', () => { + const expectedNow = DateTime.local(2024, 5, 28) + + Settings.now = () => expectedNow.toMillis() + expect( + getNextRecurringDate({ + timezone: TimezoneEnum.TzEuropeParis, + interval: RecurringTransactionIntervalEnum.Weekly, + }), + ).toBe('June 4, 2024') + }) + + it('Monthly - should return June 5th when current date is May 5th', () => { expect( getNextRecurringDate({ timezone: TimezoneEnum.TzEuropeParis, interval: RecurringTransactionIntervalEnum.Monthly, }), - ).toBe( - new Date(date.setMonth(date.getMonth() + 1)).toLocaleDateString('en-US', formatOptions), - ) + ).toBe('June 5, 2024') }) - it('should return the next quarterly date', () => { - const date = new Date() + it('Monthly - should return Feb 29th when current date is January 31st', () => { + const expectedNow = DateTime.local(2024, 1, 31) + Settings.now = () => expectedNow.toMillis() + expect( + getNextRecurringDate({ + timezone: TimezoneEnum.TzEuropeParis, + interval: RecurringTransactionIntervalEnum.Monthly, + }), + ).toBe('February 29, 2024') + }) + + it('Quarterly - should return Aug 5th when current date is May 5th', () => { expect( getNextRecurringDate({ timezone: TimezoneEnum.TzEuropeParis, interval: RecurringTransactionIntervalEnum.Quarterly, }), - ).toBe( - new Date(date.setMonth(date.getMonth() + 3)).toLocaleDateString('en-US', formatOptions), - ) + ).toBe('August 5, 2024') + }) + + it('Yearly - should return May 5, 2025 when current date is May 5, 2024', () => { + expect( + getNextRecurringDate({ + timezone: TimezoneEnum.TzEuropeParis, + interval: RecurringTransactionIntervalEnum.Yearly, + }), + ).toBe('May 5, 2025') }) - it('should return the next yearly date', () => { - const date = new Date() + it('Yearly - should return Feb 28, 2025 when current date is Feb 29, 2024', () => { + const expectedNow = DateTime.local(2024, 2, 29) + + Settings.now = () => expectedNow.toMillis() expect( getNextRecurringDate({ timezone: TimezoneEnum.TzEuropeParis, interval: RecurringTransactionIntervalEnum.Yearly, }), - ).toBe( - new Date(date.setFullYear(date.getFullYear() + 1)).toLocaleDateString( - 'en-US', - formatOptions, - ), - ) + ).toBe('February 28, 2025') }) }) diff --git a/src/components/wallets/utils.ts b/src/components/wallets/utils.ts index bad059870..d1d7c031a 100644 --- a/src/components/wallets/utils.ts +++ b/src/components/wallets/utils.ts @@ -48,30 +48,37 @@ export const getDateRef = ( export const getNextRecurringDate = ({ timezone, interval, + date, }: { timezone: TGetWordingForWalletAlert['customerTimezone'] interval?: RecurringTransactionIntervalEnum | null -}) => { - let date = null + date?: DateTime +}): string => { + let nextRecurringDate = null + const dateRef = getDateRef(timezone).set({ + day: date?.day, + month: date?.month, + year: date?.year, + }) switch (interval) { case RecurringTransactionIntervalEnum.Weekly: - date = getDateRef(timezone).plus({ days: 7 }).toLocaleString(DateTime.DATE_FULL) + nextRecurringDate = dateRef.plus({ days: 7 }) break case RecurringTransactionIntervalEnum.Monthly: - date = getDateRef(timezone).plus({ months: 1 }).toLocaleString(DateTime.DATE_FULL) + nextRecurringDate = dateRef.plus({ months: 1 }) break case RecurringTransactionIntervalEnum.Quarterly: - date = getDateRef(timezone).plus({ months: 3 }).toLocaleString(DateTime.DATE_FULL) + nextRecurringDate = dateRef.plus({ months: 3 }) break case RecurringTransactionIntervalEnum.Yearly: - date = getDateRef(timezone).plus({ years: 1 }).toLocaleString(DateTime.DATE_FULL) + nextRecurringDate = dateRef.plus({ years: 1 }) break default: break } - return date ?? '' + return nextRecurringDate?.toLocaleString(DateTime.DATE_FULL) ?? '' } const setStartOfSentence = ({ @@ -94,12 +101,13 @@ const setStartOfSentence = ({ ), }) } else { - const totalCreditCount = - toNumber(walletValues.recurringTransactionRules?.[0].paidCredits) + - toNumber(walletValues.recurringTransactionRules?.[0].grantedCredits) + const rrule = walletValues.recurringTransactionRules?.[0] + + const totalCreditCount = toNumber(rrule?.paidCredits) + toNumber(rrule?.grantedCredits) const nextRecurringTopUpDate = getNextRecurringDate({ timezone: customerTimezone, - interval: walletValues.recurringTransactionRules?.[0].interval, + interval: rrule?.interval, + date: rrule?.startedAt ? DateTime.fromISO(rrule?.startedAt) : undefined, }) if (recurringRulesValues?.method === RecurringTransactionMethodEnum.Fixed) { @@ -132,7 +140,14 @@ const setEndOfSentence = ({ let text = '' if (recurringRulesValues?.trigger === RecurringTransactionTriggerEnum.Interval) { - const dateRef = getDateRef(customerTimezone) + const rrule = walletValues.recurringTransactionRules?.[0] + const startedAt = rrule?.startedAt ? DateTime.fromISO(rrule?.startedAt) : undefined + + const dateRef = getDateRef(customerTimezone).set({ + day: startedAt?.day, + month: startedAt?.month, + year: startedAt?.year, + }) const isDayPotentiallyNotReachableOnEntirePeriod = dateRef.day > MINIMUM_DAYS_IN_MONTH switch (recurringRulesValues?.interval) { diff --git a/src/generated/graphql.tsx b/src/generated/graphql.tsx index ffc3f1bff..6099f271a 100644 --- a/src/generated/graphql.tsx +++ b/src/generated/graphql.tsx @@ -79,6 +79,11 @@ export type AddOn = { updatedAt: Scalars['ISO8601DateTime']['output']; }; + +export type AddOnIntegrationMappingsArgs = { + integrationId?: InputMaybe; +}; + export type AddOnCollection = { __typename?: 'AddOnCollection'; collection: Array; @@ -122,6 +127,15 @@ export enum AggregationTypeEnum { WeightedSumAgg = 'weighted_sum_agg' } +export type AnrokIntegration = { + __typename?: 'AnrokIntegration'; + apiKey: Scalars['String']['output']; + code: Scalars['String']['output']; + hasMappingsConfigured?: Maybe; + id: Scalars['ID']['output']; + name: Scalars['String']['output']; +}; + export type AppliedAddOn = { __typename?: 'AppliedAddOn'; addOn: AddOn; @@ -192,6 +206,12 @@ export type BillableMetric = { weightedInterval?: Maybe; }; + +/** Base billable metric */ +export type BillableMetricIntegrationMappingsArgs = { + integrationId?: InputMaybe; +}; + export type BillableMetricCollection = { __typename?: 'BillableMetricCollection'; collection: Array; @@ -934,6 +954,15 @@ export type CreateAdjustedFeeInput = { units?: InputMaybe; }; +/** Autogenerated input type of CreateAnrokIntegration */ +export type CreateAnrokIntegrationInput = { + apiKey: Scalars['String']['input']; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + code: Scalars['String']['input']; + name: Scalars['String']['input']; +}; + /** Autogenerated input type of CreateAppliedCoupon */ export type CreateAppliedCouponInput = { amountCents?: InputMaybe; @@ -1156,6 +1185,7 @@ export type CreateRecurringTransactionRuleInput = { interval?: InputMaybe; method?: InputMaybe; paidCredits?: InputMaybe; + startedAt?: InputMaybe; targetOngoingBalance?: InputMaybe; thresholdCredits?: InputMaybe; trigger: RecurringTransactionTriggerEnum; @@ -1190,8 +1220,10 @@ export type CreditNote = { currency: CurrencyEnum; customer: Customer; description?: Maybe; + externalIntegrationId?: Maybe; fileUrl?: Maybe; id: Scalars['ID']['output']; + integrationSyncable: Scalars['Boolean']['output']; invoice?: Maybe; issuingDate: Scalars['ISO8601Date']['output']; items: Array; @@ -2223,7 +2255,7 @@ export type GroupedChargeUsage = { units: Scalars['Float']['output']; }; -export type Integration = NetsuiteIntegration | OktaIntegration; +export type Integration = AnrokIntegration | NetsuiteIntegration | OktaIntegration; export type IntegrationCollection = { __typename?: 'IntegrationCollection'; @@ -2590,6 +2622,8 @@ export type Mutation = { createAddOn?: Maybe; /** Creates Adjusted Fee */ createAdjustedFee?: Maybe; + /** Create Anrok integration */ + createAnrokIntegration?: Maybe; /** Assigns a Coupon to a Customer */ createAppliedCoupon?: Maybe; /** Creates a new Billable metric */ @@ -2694,6 +2728,8 @@ export type Mutation = { revokeInvite?: Maybe; /** Revoke a membership */ revokeMembership?: Maybe; + /** Sync integration credit note */ + syncIntegrationCreditNote?: Maybe; /** Sync integration invoice */ syncIntegrationInvoice?: Maybe; /** Unassign a coupon from a customer */ @@ -2785,6 +2821,11 @@ export type MutationCreateAdjustedFeeArgs = { }; +export type MutationCreateAnrokIntegrationArgs = { + input: CreateAnrokIntegrationInput; +}; + + export type MutationCreateAppliedCouponArgs = { input: CreateAppliedCouponInput; }; @@ -3050,6 +3091,11 @@ export type MutationRevokeMembershipArgs = { }; +export type MutationSyncIntegrationCreditNoteArgs = { + input: SyncIntegrationCreditNoteInput; +}; + + export type MutationSyncIntegrationInvoiceArgs = { input: SyncIntegrationInvoiceInput; }; @@ -3932,6 +3978,7 @@ export type RecurringTransactionRule = { lagoId: Scalars['ID']['output']; method: RecurringTransactionMethodEnum; paidCredits: Scalars['String']['output']; + startedAt?: Maybe; targetOngoingBalance?: Maybe; thresholdCredits?: Maybe; trigger: RecurringTransactionTriggerEnum; @@ -4075,6 +4122,21 @@ export type SubsidiaryCollection = { metadata: CollectionMetadata; }; +/** Autogenerated input type of SyncIntegrationCreditNote */ +export type SyncIntegrationCreditNoteInput = { + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: InputMaybe; + creditNoteId: Scalars['ID']['input']; +}; + +/** Autogenerated return type of SyncIntegrationCreditNote */ +export type SyncIntegrationCreditNotePayload = { + __typename?: 'SyncIntegrationCreditNotePayload'; + /** A unique identifier for the client performing the mutation. */ + clientMutationId?: Maybe; + creditNoteId?: Maybe; +}; + /** Autogenerated input type of SyncIntegrationInvoice */ export type SyncIntegrationInvoiceInput = { /** A unique identifier for the client performing the mutation. */ @@ -4702,6 +4764,7 @@ export type UpdateRecurringTransactionRuleInput = { lagoId?: InputMaybe; method?: InputMaybe; paidCredits?: InputMaybe; + startedAt?: InputMaybe; targetOngoingBalance?: InputMaybe; thresholdCredits?: InputMaybe; trigger?: InputMaybe; @@ -5833,7 +5896,7 @@ export type DeleteTaxMutation = { __typename?: 'Mutation', destroyTax?: { __type export type TaxItemFragment = { __typename?: 'Tax', id: string, code: string, name: string, rate: number, autoGenerated: boolean, customersCount: number }; -export type CustomerWalletFragment = { __typename?: 'Wallet', id: string, currency: CurrencyEnum, rateAmount: number, expirationAt?: any | null, name?: string | null, balanceCents: any, consumedAmountCents: any, consumedCredits: number, createdAt: any, creditsBalance: number, lastBalanceSyncAt?: any | null, lastConsumedCreditAt?: any | null, status: WalletStatusEnum, terminatedAt?: any | null, ongoingBalanceCents: any, creditsOngoingBalance: number, ongoingUsageBalanceCents: any, creditsOngoingUsageBalance: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null }> | null }; +export type CustomerWalletFragment = { __typename?: 'Wallet', id: string, currency: CurrencyEnum, rateAmount: number, expirationAt?: any | null, name?: string | null, balanceCents: any, consumedAmountCents: any, consumedCredits: number, createdAt: any, creditsBalance: number, lastBalanceSyncAt?: any | null, lastConsumedCreditAt?: any | null, status: WalletStatusEnum, terminatedAt?: any | null, ongoingBalanceCents: any, creditsOngoingBalance: number, ongoingUsageBalanceCents: any, creditsOngoingUsageBalance: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null, startedAt?: any | null }> | null }; export type GetCustomerWalletListQueryVariables = Exact<{ customerId: Scalars['ID']['input']; @@ -5842,7 +5905,7 @@ export type GetCustomerWalletListQueryVariables = Exact<{ }>; -export type GetCustomerWalletListQuery = { __typename?: 'Query', wallets: { __typename?: 'WalletCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'Wallet', id: string, currency: CurrencyEnum, rateAmount: number, expirationAt?: any | null, name?: string | null, balanceCents: any, consumedAmountCents: any, consumedCredits: number, createdAt: any, creditsBalance: number, lastBalanceSyncAt?: any | null, lastConsumedCreditAt?: any | null, status: WalletStatusEnum, terminatedAt?: any | null, ongoingBalanceCents: any, creditsOngoingBalance: number, ongoingUsageBalanceCents: any, creditsOngoingUsageBalance: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null }> | null }> } }; +export type GetCustomerWalletListQuery = { __typename?: 'Query', wallets: { __typename?: 'WalletCollection', metadata: { __typename?: 'CollectionMetadata', currentPage: number, totalPages: number }, collection: Array<{ __typename?: 'Wallet', id: string, currency: CurrencyEnum, rateAmount: number, expirationAt?: any | null, name?: string | null, balanceCents: any, consumedAmountCents: any, consumedCredits: number, createdAt: any, creditsBalance: number, lastBalanceSyncAt?: any | null, lastConsumedCreditAt?: any | null, status: WalletStatusEnum, terminatedAt?: any | null, ongoingBalanceCents: any, creditsOngoingBalance: number, ongoingUsageBalanceCents: any, creditsOngoingUsageBalance: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null, startedAt?: any | null }> | null }> } }; export type TerminateCustomerWalletMutationVariables = Exact<{ input: TerminateCustomerWalletInput; @@ -6355,7 +6418,7 @@ export type GetSubscriptionForDetailsQueryVariables = Exact<{ export type GetSubscriptionForDetailsQuery = { __typename?: 'Query', subscription?: { __typename?: 'Subscription', id: string, name?: string | null, status?: StatusTypeEnum | null, externalId: string, plan: { __typename?: 'Plan', id: string, name: string, code: string, parent?: { __typename?: 'Plan', id: string, name: string, code: string } | null }, customer: { __typename?: 'Customer', id: string } } | null }; -export type WalletForUpdateFragment = { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null }> | null }; +export type WalletForUpdateFragment = { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null, startedAt?: any | null }> | null }; export type GetCustomerInfosForWalletFormQueryVariables = Exact<{ id: Scalars['ID']['input']; @@ -6369,7 +6432,7 @@ export type GetWalletInfosForWalletFormQueryVariables = Exact<{ }>; -export type GetWalletInfosForWalletFormQuery = { __typename?: 'Query', wallet?: { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null }> | null } | null }; +export type GetWalletInfosForWalletFormQuery = { __typename?: 'Query', wallet?: { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null, startedAt?: any | null }> | null } | null }; export type CreateCustomerWalletMutationVariables = Exact<{ input: CreateCustomerWalletInput; @@ -6383,7 +6446,7 @@ export type UpdateCustomerWalletMutationVariables = Exact<{ }>; -export type UpdateCustomerWalletMutation = { __typename?: 'Mutation', updateCustomerWallet?: { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null }> | null } | null }; +export type UpdateCustomerWalletMutation = { __typename?: 'Mutation', updateCustomerWallet?: { __typename?: 'Wallet', id: string, expirationAt?: any | null, name?: string | null, rateAmount: number, recurringTransactionRules?: Array<{ __typename?: 'RecurringTransactionRule', lagoId: string, method: RecurringTransactionMethodEnum, trigger: RecurringTransactionTriggerEnum, interval?: RecurringTransactionIntervalEnum | null, targetOngoingBalance?: string | null, paidCredits: string, grantedCredits: string, thresholdCredits?: string | null, startedAt?: any | null }> | null } | null }; export type CreatePasswordResetMutationVariables = Exact<{ input: CreatePasswordResetInput; @@ -6519,7 +6582,7 @@ export type GetAuthIntegrationsQueryVariables = Exact<{ }>; -export type GetAuthIntegrationsQuery = { __typename?: 'Query', organization?: { __typename?: 'CurrentOrganization', id: string, premiumIntegrations: Array } | null, integrations?: { __typename?: 'IntegrationCollection', collection: Array<{ __typename?: 'NetsuiteIntegration' } | { __typename?: 'OktaIntegration', id: string, domain: string, clientId?: string | null, clientSecret?: string | null, organizationName: string, name: string }> } | null }; +export type GetAuthIntegrationsQuery = { __typename?: 'Query', organization?: { __typename?: 'CurrentOrganization', id: string, premiumIntegrations: Array } | null, integrations?: { __typename?: 'IntegrationCollection', collection: Array<{ __typename?: 'AnrokIntegration' } | { __typename?: 'NetsuiteIntegration' } | { __typename?: 'OktaIntegration', id: string, domain: string, clientId?: string | null, clientSecret?: string | null, organizationName: string, name: string }> } | null }; export type OktaIntegrationDetailsFragment = { __typename?: 'OktaIntegration', id: string, clientId?: string | null, clientSecret?: string | null, code: string, organizationName: string, domain: string, name: string }; @@ -6528,7 +6591,7 @@ export type GetOktaIntegrationQueryVariables = Exact<{ }>; -export type GetOktaIntegrationQuery = { __typename?: 'Query', integration?: { __typename?: 'NetsuiteIntegration' } | { __typename?: 'OktaIntegration', id: string, clientId?: string | null, clientSecret?: string | null, code: string, organizationName: string, domain: string, name: string } | null }; +export type GetOktaIntegrationQuery = { __typename?: 'Query', integration?: { __typename?: 'AnrokIntegration' } | { __typename?: 'NetsuiteIntegration' } | { __typename?: 'OktaIntegration', id: string, clientId?: string | null, clientSecret?: string | null, code: string, organizationName: string, domain: string, name: string } | null }; export type GocardlessIntegrationDetailsFragment = { __typename?: 'GocardlessProvider', id: string, code: string, name: string, successRedirectUrl?: string | null, webhookSecret?: string | null }; @@ -7350,6 +7413,7 @@ export const WalletForUpdateFragmentDoc = gql` paidCredits grantedCredits thresholdCredits + startedAt } } `; diff --git a/src/pages/WalletForm/WalletForm.tsx b/src/pages/WalletForm/WalletForm.tsx index d491b06f3..e34194782 100644 --- a/src/pages/WalletForm/WalletForm.tsx +++ b/src/pages/WalletForm/WalletForm.tsx @@ -1,5 +1,6 @@ import { gql } from '@apollo/client' import { useFormik } from 'formik' +import { DateTime } from 'luxon' import { useEffect, useMemo, useRef, useState } from 'react' import { generatePath, useNavigate, useParams } from 'react-router-dom' import styled from 'styled-components' @@ -13,6 +14,7 @@ import { FORM_TYPE_ENUM } from '~/core/constants/form' import { CUSTOMER_DETAILS_TAB_ROUTE } from '~/core/router' import { getCurrencyPrecision } from '~/core/serializers/serializeAmount' import { + CreateRecurringTransactionRuleInput, CurrencyEnum, GetWalletInfosForWalletFormQuery, LagoApiError, @@ -53,6 +55,7 @@ gql` paidCredits grantedCredits thresholdCredits + startedAt } } @@ -198,35 +201,47 @@ const WalletForm = () => { recurringTransactionRules, ...values }) => { - const recurringTransactionRulesFormatted = !!recurringTransactionRules?.length - ? recurringTransactionRules?.map((rule: UpdateRecurringTransactionRuleInput) => { - const { - lagoId, - interval, - trigger, - thresholdCredits, - method, - targetOngoingBalance, - paidCredits: rulePaidCredit, - grantedCredits: ruleGrantedCredit, - } = rule - - return { - lagoId: formType === FORM_TYPE_ENUM.edition ? lagoId : undefined, - method: method as RecurringTransactionMethodEnum, - trigger: trigger as RecurringTransactionTriggerEnum, - interval: trigger === RecurringTransactionTriggerEnum.Interval ? interval : null, - thresholdCredits: - trigger === RecurringTransactionTriggerEnum.Threshold ? thresholdCredits : null, - paidCredits: rulePaidCredit === '' ? '0' : String(rulePaidCredit), - grantedCredits: ruleGrantedCredit === '' ? '0' : String(ruleGrantedCredit), - targetOngoingBalance: - targetOngoingBalance === '' ? '0' : String(targetOngoingBalance), - } - }) - : formType === FORM_TYPE_ENUM.edition - ? [] - : null + const recurringTransactionRulesFormatted = + recurringTransactionRules && recurringTransactionRules?.length > 0 + ? recurringTransactionRules.map( + (rule: CreateRecurringTransactionRuleInput | UpdateRecurringTransactionRuleInput) => { + const { + interval, + trigger, + thresholdCredits, + method, + targetOngoingBalance, + startedAt, + paidCredits: rulePaidCredit, + grantedCredits: ruleGrantedCredit, + } = rule + + return { + lagoId: + 'lagoId' in rule && formType === FORM_TYPE_ENUM.edition + ? rule.lagoId + : undefined, + method: method as RecurringTransactionMethodEnum, + trigger: trigger as RecurringTransactionTriggerEnum, + interval: trigger === RecurringTransactionTriggerEnum.Interval ? interval : null, + startedAt: + trigger === RecurringTransactionTriggerEnum.Interval + ? startedAt ?? DateTime.now().toISO() + : null, + thresholdCredits: + trigger === RecurringTransactionTriggerEnum.Threshold ? thresholdCredits : null, + paidCredits: rulePaidCredit === '' ? '0' : String(rulePaidCredit), + grantedCredits: ruleGrantedCredit === '' ? '0' : String(ruleGrantedCredit), + targetOngoingBalance: + method === RecurringTransactionMethodEnum.Target + ? targetOngoingBalance === '' + ? '0' + : String(targetOngoingBalance) + : null, + } + }, + ) + : [] if (formType === FORM_TYPE_ENUM.edition) { const { errors } = await updateWallet({ diff --git a/src/pages/WalletForm/components/TopUpCard.tsx b/src/pages/WalletForm/components/TopUpCard.tsx index 584afb790..51cb31f0b 100644 --- a/src/pages/WalletForm/components/TopUpCard.tsx +++ b/src/pages/WalletForm/components/TopUpCard.tsx @@ -1,11 +1,12 @@ import { Box, InputAdornment, Stack } from '@mui/material' import { FormikProps } from 'formik' import { get } from 'lodash' +import { DateTime } from 'luxon' import { FC, RefObject, useMemo, useState } from 'react' import styled from 'styled-components' import { Accordion, Alert, Button, Icon, Typography } from '~/components/designSystem' -import { AmountInputField, ComboBox, ComboBoxField } from '~/components/form' +import { AmountInputField, ComboBox, ComboBoxField, DatePickerField } from '~/components/form' import { PremiumWarningDialogRef } from '~/components/PremiumWarningDialog' import { getWordingForWalletCreationAlert } from '~/components/wallets/utils' import { FORM_TYPE_ENUM } from '~/core/constants/form' @@ -72,7 +73,8 @@ const DEFAULT_RULES: UpdateRecurringTransactionRuleInput = { grantedCredits: '', paidCredits: '', thresholdCredits: '', - targetOngoingBalance: '', + targetOngoingBalance: null, + startedAt: DateTime.now().toISO(), } interface TopUpCardProps { @@ -189,9 +191,9 @@ export const TopUpCard: FC = ({ { + onDelete={async () => { + formikProps.setFieldValue('recurringTransactionRules', undefined) setIsRecurringTopUpEnabled(false) - formikProps.setFieldValue('recurringTransactionRules.0', undefined) }} /> } @@ -282,18 +284,6 @@ export const TopUpCard: FC = ({ ? translate('text_66584178ee91f801012606a6') : undefined } - helperText={translate('text_62d18855b22699e5cf55f88b', { - paidCredits: intlFormatNumber( - isNaN(Number(recurringTransactionRules?.paidCredits)) - ? 0 - : Number(recurringTransactionRules?.paidCredits) * - Number(formikProps.values.rateAmount), - { - currencyDisplay: 'symbol', - currency: formikProps?.values?.currency || CurrencyEnum.Usd, - }, - ), - })} {...inputAdornment(translate('text_62d18855b22699e5cf55f889'))} /> )} @@ -335,7 +325,7 @@ export const TopUpCard: FC = ({ }} /> {recurringTransactionRules?.trigger === RecurringTransactionTriggerEnum.Interval && ( - + <> = ({ }, ]} /> - + + )} {recurringTransactionRules?.trigger === RecurringTransactionTriggerEnum.Threshold && ( { return !isNaN(Number(grantedCredits)) || !isNaN(Number(rulePaidCredit)) }, }), - targetOngoingBalance: string().test({ - test: function (targetOngoingBalance, { path }) { - const { method, thresholdCredits, trigger } = this?.parent + targetOngoingBalance: string() + .nullable() + .test({ + test: function (targetOngoingBalance, { path }) { + const { method, thresholdCredits, trigger } = this?.parent - if (!!method && method !== RecurringTransactionMethodEnum.Target) { - return true - } + if (!!method && method !== RecurringTransactionMethodEnum.Target) { + return true + } - if ( - !!thresholdCredits && - trigger === RecurringTransactionTriggerEnum.Threshold && - !!targetOngoingBalance && - Number(targetOngoingBalance) < Number(thresholdCredits) - ) { - return this.createError({ - path, - message: walletFormErrorCodes.targetOngoingBalanceShouldBeGreaterThanThreshold, - }) - } + if (!targetOngoingBalance && method === RecurringTransactionMethodEnum.Target) { + return this.createError() + } - return !isNaN(Number(targetOngoingBalance)) - }, - }), + if ( + !!thresholdCredits && + trigger === RecurringTransactionTriggerEnum.Threshold && + !!targetOngoingBalance && + Number(targetOngoingBalance) < Number(thresholdCredits) + ) { + return this.createError({ + path, + message: walletFormErrorCodes.targetOngoingBalanceShouldBeGreaterThanThreshold, + }) + } + + return !isNaN(Number(targetOngoingBalance)) + }, + }), + startedAt: string() + .nullable() + .test({ + test: function (startedAt, { path }) { + const { trigger } = this?.parent + + if (!!trigger && trigger !== RecurringTransactionTriggerEnum.Interval) { + return true + } + + if (startedAt && !DateTime.fromISO(startedAt).isValid) { + return this.createError({ + path, + message: dateErrorCodes.wrongFormat, + }) + } + + return true + }, + }), }), ) .nullable(),