Skip to content

Commit 6db8871

Browse files
authored
feat(sdk): migrate billing screens to connect rpc (#1276)
1 parent 9a1de40 commit 6db8871

File tree

6 files changed

+241
-262
lines changed

6 files changed

+241
-262
lines changed

sdks/js/packages/core/react/components/organization/billing/cycle-switch/index.tsx

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useEffect, useMemo, useState } from 'react';
1+
import { useCallback, useMemo } from 'react';
22
import {
33
Button,
44
Skeleton,
@@ -13,12 +13,10 @@ import { useFrontier } from '~/react/contexts/FrontierContext';
1313
import { getPlanIntervalName, getPlanPrice } from '~/react/utils';
1414
import * as _ from 'lodash';
1515
import { usePlans } from '../../plans/hooks/usePlans';
16-
import dayjs from 'dayjs';
1716
import { DEFAULT_DATE_FORMAT } from '~/react/utils/constants';
1817
import cross from '~/react/assets/cross.svg';
1918
import styles from '../../organization.module.css';
2019
import { timestampToDayjs } from '~/utils/timestamp';
21-
import { Plan } from '@raystack/proton/frontier';
2220

2321
export function ConfirmCycleSwitch() {
2422
const {
@@ -33,8 +31,6 @@ export function ConfirmCycleSwitch() {
3331
const { planId } = useParams({ from: '/billing/cycle-switch/$planId' });
3432
const dateFormat = config?.dateFormat || DEFAULT_DATE_FORMAT;
3533

36-
const [isCycleSwitching, setCycleSwitching] = useState(false);
37-
3834
const closeModal = useCallback(
3935
() => navigate({ to: '/billing' }),
4036
[navigate]
@@ -71,46 +67,35 @@ export function ConfirmCycleSwitch() {
7167
const isLoading = isAllPlansLoading;
7268

7369
async function onConfirm() {
74-
setCycleSwitching(true);
75-
try {
76-
if (nextPlan?.id) {
77-
const nextPlanId = nextPlan?.id;
78-
if (isPaymentMethodRequired) {
79-
checkoutPlan({
80-
planId: nextPlanId,
81-
isTrial: false,
82-
onSuccess: data => {
83-
window.location.href = data?.checkout_url as string;
84-
}
85-
});
86-
} else
87-
changePlan({
88-
planId: nextPlanId,
89-
onSuccess: async () => {
90-
const planPhase = await verifyPlanChange({
91-
planId: nextPlanId
92-
});
93-
if (planPhase) {
94-
closeModal();
95-
const changeDate = timestampToDayjs(
96-
planPhase?.effectiveAt
97-
)?.format(dateFormat);
98-
toast.success(`Plan cycle switch successful`, {
99-
description: `Your plan cycle will switched to ${nextPlanIntervalName} on ${changeDate}`
100-
});
101-
}
102-
},
103-
immediate: isUpgrade
70+
const nextPlanId = nextPlan?.id;
71+
if (!nextPlanId) return;
72+
if (isPaymentMethodRequired) {
73+
checkoutPlan({
74+
planId: nextPlanId,
75+
isTrial: false,
76+
onSuccess: data => {
77+
window.location.href = data?.checkoutUrl as string;
78+
}
79+
});
80+
} else
81+
changePlan({
82+
planId: nextPlanId,
83+
onSuccess: async () => {
84+
const planPhase = await verifyPlanChange({
85+
planId: nextPlanId
10486
});
105-
}
106-
} catch (err: any) {
107-
console.error(err);
108-
toast.error('Something went wrong', {
109-
description: err.message
87+
if (planPhase) {
88+
closeModal();
89+
const changeDate = timestampToDayjs(planPhase?.effectiveAt)?.format(
90+
dateFormat
91+
);
92+
toast.success(`Plan cycle switch successful`, {
93+
description: `Your plan cycle will switched to ${nextPlanIntervalName} on ${changeDate}`
94+
});
95+
}
96+
},
97+
immediate: isUpgrade
11098
});
111-
} finally {
112-
setCycleSwitching(false);
113-
}
11499
}
115100

116101
const cycleSwitchDate = activeSubscription?.currentPeriodEndAt
@@ -126,7 +111,7 @@ export function ConfirmCycleSwitch() {
126111
style={{ padding: 0, maxWidth: '600px', width: '100%' }}
127112
>
128113
<Dialog.Header>
129-
<Flex justify="between" align="center" style={{ width: '100%' }}>
114+
<Flex justify="between" align="center" width="full">
130115
<Text size="large" weight="medium">
131116
Switch billing cycle
132117
</Text>
@@ -185,9 +170,9 @@ export function ConfirmCycleSwitch() {
185170
Cancel
186171
</Button>
187172
<Button
188-
disabled={isLoading || isCycleSwitching || isPlanActionLoading}
173+
disabled={isLoading || isPlanActionLoading}
189174
onClick={onConfirm}
190-
loading={isCycleSwitching}
175+
loading={isPlanActionLoading}
191176
loaderText="Switching..."
192177
data-test-id="frontier-sdk-billing-cycle-switch-submit-button"
193178
>

sdks/js/packages/core/react/components/organization/billing/index.tsx

Lines changed: 78 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,17 @@ import {
1010
import { Outlet } from '@tanstack/react-router';
1111
import { styles } from '../styles';
1212
import { useFrontier } from '~/react/contexts/FrontierContext';
13-
import { useCallback, useEffect, useState } from 'react';
13+
import { useCallback, useEffect } from 'react';
1414
import billingStyles from './billing.module.css';
1515
import {
16-
V1Beta1CheckoutSetupBody,
17-
V1Beta1Invoice
18-
} from '~/src';
19-
import { BillingAccount } from '@raystack/proton/frontier';
20-
// import { converBillingAddressToString } from '~/react/utils';
16+
BillingAccount,
17+
ListInvoicesRequestSchema,
18+
FrontierServiceQueries,
19+
CreateCheckoutRequestSchema
20+
} from '@raystack/proton/frontier';
21+
import { useQuery as useConnectQuery } from '@connectrpc/connect-query';
22+
import { create } from '@bufbuild/protobuf';
23+
import { useMutation } from '~hooks';
2124
import Invoices from './invoices';
2225
import qs from 'query-string';
2326

@@ -84,13 +87,12 @@ const BillingDetails = ({
8487
isAllowed,
8588
disabled = false
8689
}: BillingDetailsProps) => {
87-
// const addressStr = converBillingAddressToString(billingAccount?.address);
8890
const btnText =
8991
billingAccount?.email || billingAccount?.name ? 'Update' : 'Add details';
9092
const isButtonDisabled = isLoading || disabled;
9193
return (
9294
<div className={billingStyles.detailsBox}>
93-
<Flex align="center" justify="between" style={{ width: '100%' }}>
95+
<Flex align="center" justify="between" width="full">
9496
<Text className={billingStyles.detailsBoxHeading}>Billing Details</Text>
9597
{isAllowed ? (
9698
<Tooltip
@@ -131,7 +133,6 @@ export default function Billing() {
131133
const {
132134
billingAccount,
133135
isBillingAccountLoading,
134-
client,
135136
config,
136137
activeSubscription,
137138
isActiveSubscriptionLoading,
@@ -140,83 +141,91 @@ export default function Billing() {
140141
isOrganizationKycLoading
141142
} = useFrontier();
142143

143-
const [invoices, setInvoices] = useState<V1Beta1Invoice[]>([]);
144-
const [isInvoicesLoading, setIsInvoicesLoading] = useState(false);
145144
const { isAllowed, isFetching } = useBillingPermission();
146145

147-
const fetchInvoices = useCallback(
148-
async (organizationId: string, billingId: string) => {
149-
setIsInvoicesLoading(true);
150-
try {
151-
const resp = await client?.frontierServiceListInvoices(
152-
organizationId,
153-
billingId,
154-
{ nonzero_amount_only: true }
155-
);
156-
const newInvoices = resp?.data?.invoices || [];
157-
setInvoices(newInvoices);
158-
} catch (err) {
159-
console.error(err);
160-
} finally {
161-
setIsInvoicesLoading(false);
162-
}
163-
},
164-
[client]
146+
const {
147+
data: invoices = [],
148+
isLoading: isInvoicesLoading,
149+
error: invoicesError
150+
} = useConnectQuery(
151+
FrontierServiceQueries.listInvoices,
152+
create(ListInvoicesRequestSchema, {
153+
orgId: billingAccount?.orgId || '',
154+
billingId: billingAccount?.id || '',
155+
nonzeroAmountOnly: true
156+
}),
157+
{
158+
enabled: !!billingAccount?.id && !!billingAccount?.orgId,
159+
select: data => data?.invoices || []
160+
}
165161
);
166162

167163
useEffect(() => {
168-
if (billingAccount?.id && billingAccount?.orgId) {
169-
fetchInvoices(billingAccount?.orgId, billingAccount?.id);
164+
if (invoicesError) {
165+
toast.error('Failed to load invoices', {
166+
description: invoicesError?.message
167+
});
168+
}
169+
}, [invoicesError]);
170+
171+
const { mutateAsync: createCheckoutMutation } = useMutation(
172+
FrontierServiceQueries.createCheckout,
173+
{
174+
onError: (err: Error) => {
175+
console.error(err);
176+
toast.error('Something went wrong', {
177+
description: err?.message
178+
});
179+
}
170180
}
171-
}, [billingAccount?.id, billingAccount?.orgId, client, fetchInvoices]);
181+
);
172182

173183
const onAddDetailsClick = useCallback(async () => {
174184
const orgId = billingAccount?.orgId || '';
175185
const billingAccountId = billingAccount?.id || '';
176-
if (billingAccountId && orgId) {
177-
try {
178-
const query = qs.stringify(
179-
{
180-
details: btoa(
181-
qs.stringify({
182-
billing_id: billingAccount?.id,
183-
organization_id: billingAccount?.orgId,
184-
type: 'billing'
185-
})
186-
),
187-
checkout_id: '{{.CheckoutID}}'
188-
},
189-
{ encode: false }
190-
);
191-
const cancel_url = `${config?.billing?.cancelUrl}?${query}`;
192-
const success_url = `${config?.billing?.successUrl}?${query}`;
186+
if (!billingAccountId || !orgId) return;
193187

194-
const setup_body: V1Beta1CheckoutSetupBody = {
195-
customer_portal: true
196-
};
188+
try {
189+
const query = qs.stringify(
190+
{
191+
details: btoa(
192+
qs.stringify({
193+
billing_id: billingAccount?.id,
194+
organization_id: billingAccount?.orgId,
195+
type: 'billing'
196+
})
197+
),
198+
checkout_id: '{{.CheckoutID}}'
199+
},
200+
{ encode: false }
201+
);
202+
const cancel_url = `${config?.billing?.cancelUrl}?${query}`;
203+
const success_url = `${config?.billing?.successUrl}?${query}`;
197204

198-
const resp = await client?.frontierServiceCreateCheckout(
199-
billingAccount?.orgId || '',
200-
billingAccount?.id || '',
201-
{
202-
cancel_url,
203-
success_url,
204-
setup_body
205+
const resp = await createCheckoutMutation(
206+
create(CreateCheckoutRequestSchema, {
207+
orgId: billingAccount?.orgId || '',
208+
billingId: billingAccount?.id || '',
209+
cancelUrl: cancel_url,
210+
successUrl: success_url,
211+
setupBody: {
212+
paymentMethod: false,
213+
customerPortal: true
205214
}
206-
);
207-
const checkout_url = resp?.data?.checkout_session?.checkout_url;
208-
if (checkout_url) {
209-
window.location.href = checkout_url;
210-
}
211-
} catch (err) {
212-
console.error(err);
213-
toast.error('Something went wrong');
215+
})
216+
);
217+
const checkoutUrl = resp?.checkoutSession?.checkoutUrl;
218+
if (checkoutUrl) {
219+
window.location.href = checkoutUrl;
214220
}
221+
} catch (err) {
222+
console.error(err);
223+
toast.error('Something went wrong');
215224
}
216225
}, [
217226
billingAccount?.id,
218227
billingAccount?.orgId,
219-
client,
228+
createCheckoutMutation,
220229
config?.billing?.cancelUrl,
221230
config?.billing?.successUrl
222231
]);
@@ -231,7 +240,7 @@ export default function Billing() {
231240
const isOrganizationKycCompleted = organizationKyc?.status === true;
232241

233242
return (
234-
<Flex direction="column" style={{ width: '100%' }}>
243+
<Flex direction="column" width="full">
235244
<Flex style={styles.header}>
236245
<Text size="large">Billing</Text>
237246
</Flex>

0 commit comments

Comments
 (0)