Skip to content

Commit f1a589f

Browse files
committed
feat: add seats page in customer portal
1 parent d8a22bf commit f1a589f

File tree

8 files changed

+815
-21
lines changed

8 files changed

+815
-21
lines changed

clients/apps/web/src/app/(main)/[organization]/portal/Navigation.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ const links = (organization: schemas['Organization']) => [
2424
label: 'Orders',
2525
isActive: (path: string) => path.includes('/orders'),
2626
},
27+
{
28+
href: `/${organization.slug}/portal/seats`,
29+
label: 'Seats',
30+
isActive: (path: string) => path.includes('/seats'),
31+
},
2732
{
2833
href: `/${organization.slug}/portal/usage`,
2934
label: 'Usage',

clients/apps/web/src/hooks/queries/customerPortal.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,3 +398,83 @@ export const useCustomerOrderPaymentStatus = (api: Client) =>
398398
params: { path: { id: variables.orderId } },
399399
}),
400400
})
401+
402+
export const useCustomerSeats = (
403+
api: Client,
404+
subscriptionId: string | undefined,
405+
) =>
406+
useQuery({
407+
queryKey: ['customer_seats', { subscription_id: subscriptionId }],
408+
queryFn: () =>
409+
unwrap(
410+
api.GET('/v1/customer-portal/seats', {
411+
params: { query: { subscription_id: subscriptionId! } },
412+
}),
413+
),
414+
retry: defaultRetry,
415+
enabled: !!subscriptionId,
416+
})
417+
418+
export const useAssignSeat = (api: Client) =>
419+
useMutation({
420+
mutationFn: async (variables: {
421+
subscription_id: string
422+
email?: string
423+
external_customer_id?: string
424+
customer_id?: string
425+
metadata?: Record<string, any>
426+
}) =>
427+
api.POST('/v1/customer-portal/seats', {
428+
body: variables,
429+
}),
430+
onSuccess: async (result, _variables, _ctx) => {
431+
if (result.error) {
432+
return
433+
}
434+
queryClient.invalidateQueries({
435+
queryKey: ['customer_seats'],
436+
})
437+
},
438+
})
439+
440+
export const useRevokeSeat = (api: Client) =>
441+
useMutation({
442+
mutationFn: async (seatId: string) => {
443+
const result = await api.DELETE('/v1/customer-portal/seats/{seat_id}', {
444+
params: { path: { seat_id: seatId } },
445+
})
446+
if (result.error) {
447+
const errorMessage =
448+
typeof result.error.detail === 'string'
449+
? result.error.detail
450+
: 'Failed to revoke seat'
451+
throw new Error(errorMessage)
452+
}
453+
return result
454+
},
455+
onSuccess: async (_result, _variables, _ctx) => {
456+
queryClient.invalidateQueries({
457+
queryKey: ['customer_seats'],
458+
})
459+
},
460+
})
461+
462+
export const useResendSeatInvitation = (api: Client) =>
463+
useMutation({
464+
mutationFn: async (seatId: string) => {
465+
const result = await api.POST(
466+
'/v1/customer-portal/seats/{seat_id}/resend',
467+
{
468+
params: { path: { seat_id: seatId } },
469+
},
470+
)
471+
if (result.error) {
472+
const errorMessage =
473+
typeof result.error.detail === 'string'
474+
? result.error.detail
475+
: 'Failed to resend invitation'
476+
throw new Error(errorMessage)
477+
}
478+
return result
479+
},
480+
})

0 commit comments

Comments
 (0)