chore: remove unused billing page/queries (#19412)

This commit is contained in:
Kevin Grüneberg authored and GitHub committed 2023-12-04 15:54:57 +01:00
1 parent c43f0e74a3
commit 598c1ef866
7 files changed
-453

No files matched your search

@@ -1,159 +0,0 @@
import { useParams } from 'common'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { Button, IconChevronLeft, IconChevronRight, IconDownload, IconFileText } from 'ui'
import Table from 'components/to-be-cleaned/Table'
import AlertError from 'components/ui/AlertError'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
import { getInvoice } from 'data/invoices/invoice-query'
import { useProjectInvoicesCountQuery } from 'data/invoices/project-invoices-count-query'
import { useProjectInvoicesQuery } from 'data/invoices/project-invoices-query'
import { useStore } from 'hooks'
import { InvoiceStatus } from './Invoices.types'
import InvoiceStatusBadge from './InvoiceStatusBadge'
const PAGE_LIMIT = 10
const Invoices = () => {
const { ui } = useStore()
const { ref: projectRef } = useParams()
const [page, setPage] = useState(1)
const offset = (page - 1) * PAGE_LIMIT
const { data: count, isError: isErrorCount } = useProjectInvoicesCountQuery({ projectRef })
const { data, error, isLoading, isError, isSuccess } = useProjectInvoicesQuery({
projectRef,
limit: PAGE_LIMIT,
offset,
})
const invoices = data || []
useEffect(() => {
setPage(1)
}, [projectRef])
const fetchInvoice = async (id: string) => {
try {
const invoice = await getInvoice({ id })
if (invoice?.invoice_pdf) window.open(invoice.invoice_pdf, '_blank')
} catch (error: any) {
ui.setNotification({
category: 'info',
message: `Failed to fetch the selected invoice: ${error.message}`,
})
}
}
return (
<div className="container my-4 max-w-4xl space-y-1">
{isLoading && <GenericSkeletonLoader />}
{isError && <AlertError error={error} subject="Failed to retrieve project invoices" />}
{isSuccess && (
<Table
head={[
<Table.th key="header-icon"></Table.th>,
<Table.th key="header-date">Date</Table.th>,
<Table.th key="header-amount">Amount due</Table.th>,
<Table.th key="header-invoice">Invoice number</Table.th>,
<Table.th key="header-status" className="flex items-center">
Status
</Table.th>,
<Table.th key="header-download" className="text-right"></Table.th>,
]}
body={
invoices.length === 0 ? (
<Table.tr>
<Table.td colSpan={6} className="p-3 py-12 text-center">
<p className="text-foreground-light">No invoices for this project yet</p>
</Table.td>
</Table.tr>
) : (
<>
{invoices.map((x) => {
return (
<Table.tr key={x.id}>
<Table.td>
<IconFileText size="xxl" />
</Table.td>
<Table.td>
<p>{new Date(x.period_end * 1000).toLocaleString()}</p>
</Table.td>
<Table.td>
<p>
{x.subtotal >= 0
? `$${x.subtotal / 100}`
: `-$${Math.abs(x.subtotal / 100)}`}
</p>
</Table.td>
<Table.td>
<p>{x.number}</p>
</Table.td>
<Table.td>
<InvoiceStatusBadge status={x.status} />
</Table.td>
<Table.td className="align-right">
<div className="flex items-center justify-end space-x-2">
{[InvoiceStatus.UNCOLLECTIBLE, InvoiceStatus.OPEN].includes(x.status) && (
<Button asChild>
<Link
href={`https://redirect.revops.supabase.com/pay-invoice/${x.id}`}
target="_blank"
rel="noreferrer"
>
Pay Now
</Link>
</Button>
)}
<Button
type="outline"
icon={<IconDownload size={16} strokeWidth={1.5} />}
onClick={() => fetchInvoice(x.id)}
/>
</div>
</Table.td>
</Table.tr>
)
})}
<Table.tr key="navigation">
<Table.td colSpan={6}>
<div className="flex items-center justify-between">
<p className="text-sm opacity-50">
{isErrorCount
? 'Failed to retrieve total number of invoices'
: `Showing ${offset + 1} to ${
offset + invoices.length
} out of ${count} invoices`}
</p>
<div className="flex items-center space-x-2">
<Button
icon={<IconChevronLeft />}
type="default"
size="tiny"
disabled={page === 1}
onClick={async () => setPage(page - 1)}
/>
<Button
icon={<IconChevronRight />}
type="default"
size="tiny"
disabled={page * PAGE_LIMIT >= (count ?? 0)}
onClick={async () => setPage(page + 1)}
/>
</div>
</div>
</Table.td>
</Table.tr>
</>
)
}
/>
)}
</div>
)
}
export default Invoices
-25
View File
@@ -57,31 +57,6 @@ export const analyticsKeys = {
},
] as const,
dailyStats: (
projectRef: string | undefined,
{
attribute,
startDate,
endDate,
interval,
}: {
attribute?: string
startDate?: string
endDate?: string
interval?: string
}
) =>
[
'projects',
projectRef,
'daily-stats',
{
attribute,
startDate: isoDateStringToDate(startDate),
endDate: isoDateStringToDate(endDate),
interval,
},
] as const,
orgDailyStats: (
orgSlug: string | undefined,
{
@@ -1,43 +0,0 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { head } from 'data/fetchers'
import { ResponseError } from 'types'
import { invoicesKeys } from './keys'
export type ProjectInvoicesCountVariables = {
projectRef?: string
}
export async function getProjectInvoicesCount(
{ projectRef }: ProjectInvoicesCountVariables,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('Project ref is required')
const res = await head(`/platform/projects/{ref}/invoices`, {
params: { path: { ref: projectRef } },
signal,
parseAs: 'text',
})
if (res.error) throw (res as any).error
return Number(res.response.headers.get('X-Total-Count'))
}
export type ProjectInvoicesCountData = Awaited<ReturnType<typeof getProjectInvoicesCount>>
export type ProjectInvoicesCountError = ResponseError
export const useProjectInvoicesCountQuery = <TData = ProjectInvoicesCountData>(
{ projectRef }: ProjectInvoicesCountVariables,
{
enabled = true,
...options
}: UseQueryOptions<ProjectInvoicesCountData, ProjectInvoicesCountError, TData> = {}
) =>
useQuery<ProjectInvoicesCountData, ProjectInvoicesCountError, TData>(
invoicesKeys.projectInvoicesCount(projectRef),
({ signal }) => getProjectInvoicesCount({ projectRef }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
@@ -1,48 +0,0 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { get } from 'data/fetchers'
import { ResponseError } from 'types'
import { invoicesKeys } from './keys'
export type ProjectInvoicesVariables = {
projectRef?: string
offset?: number
limit?: number
}
export async function getProjectInvoices(
{ projectRef, offset = 0, limit = 10 }: ProjectInvoicesVariables,
signal?: AbortSignal
) {
if (!projectRef) throw new Error('Project ref is required')
const { data, error } = await get(`/platform/projects/{ref}/invoices`, {
params: {
path: { ref: projectRef },
query: { offset: offset.toString(), limit: limit.toString() },
},
signal,
})
if (error) throw error
return data
}
export type ProjectInvoicesData = Awaited<ReturnType<typeof getProjectInvoices>>
export type ProjectInvoicesError = ResponseError
export const useProjectInvoicesQuery = <TData = ProjectInvoicesData>(
{ projectRef, offset, limit }: ProjectInvoicesVariables,
{
enabled = true,
...options
}: UseQueryOptions<ProjectInvoicesData, ProjectInvoicesError, TData> = {}
) =>
// [Joshen] Switch to useInfiniteQuery
useQuery<ProjectInvoicesData, ProjectInvoicesError, TData>(
invoicesKeys.projectInvoices(projectRef, offset),
({ signal }) => getProjectInvoices({ projectRef, offset, limit }, signal),
{
enabled: enabled && typeof projectRef !== 'undefined',
...options,
}
)
-3
View File
@@ -11,9 +11,6 @@ export const organizationKeys = {
slug: string | undefined,
{ date_start, date_end }: { date_start: string | undefined; date_end: string | undefined }
) => ['organizations', slug, 'audit-logs', { date_start, date_end }] as const,
migrateBilling: (slug: string | undefined) => ['organizations', slug, 'migrate-billing'] as const,
migrateBillingPreview: (slug: string | undefined) =>
['organizations', slug, 'migrate-billing', 'preview'] as const,
subscriptionPreview: (slug: string | undefined, tier: string | undefined) =>
['organizations', slug, 'subscription', 'preview', tier] as const,
taxIds: (slug: string | undefined) => ['organizations', slug, 'tax-ids'] as const,
@@ -1,76 +0,0 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import { toast } from 'react-hot-toast'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { ResponseError } from 'types'
import { organizationKeys } from './keys'
export type OrganizationBillingMigrationVariables = {
organizationSlug?: string
tier?: string
paymentMethodId?: string
}
export async function migrateBilling({
organizationSlug,
tier,
paymentMethodId,
}: OrganizationBillingMigrationVariables) {
if (!organizationSlug) throw new Error('organizationSlug is required')
if (!tier) throw new Error('tier is required')
const payload: { tier: string; payment_method_id?: string } = {
tier,
payment_method_id: paymentMethodId,
}
const response = await post(
`${API_URL}/organizations/${organizationSlug}/org-billing-migration`,
payload
)
if (response.error) throw response.error
return response
}
type OrganizationBillingMigrationData = Awaited<ReturnType<typeof migrateBilling>>
export const useOrganizationBillingMigrationMutation = ({
onSuccess,
onError,
...options
}: Omit<
UseMutationOptions<
OrganizationBillingMigrationData,
ResponseError,
OrganizationBillingMigrationVariables
>,
'mutationFn'
> = {}) => {
const queryClient = useQueryClient()
return useMutation<
OrganizationBillingMigrationData,
ResponseError,
OrganizationBillingMigrationVariables
>((vars) => migrateBilling(vars), {
async onSuccess(data, variables, context) {
const { organizationSlug } = variables
await Promise.all([
queryClient.invalidateQueries(organizationKeys.members(organizationSlug)),
queryClient.invalidateQueries(organizationKeys.list()),
queryClient.invalidateQueries(organizationKeys.freeProjectLimitCheck(organizationSlug)),
])
await onSuccess?.(data, variables, context)
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to migrate billing: ${data.message}`)
} else {
onError(data, variables, context)
}
},
...options,
})
}
@@ -1,99 +0,0 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query'
import { post } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { organizationKeys } from './keys'
import { SubscriptionTier } from 'data/subscriptions/types'
export type OrganizationBillingMigrationPreviewVariables = {
organizationSlug?: string
tier?: string
}
export type OrganizationBillingMigrationPreviewResponse = {
added_credits: number
addons_to_be_removed: {
projectRef: string
projectName: string
addons: { variant: string; name: string; type: string }[]
}[]
monthly_invoice_breakdown: {
description: string
unit_price: number
quantity: number
total_price: number
}[]
old_tiers: SubscriptionTier[]
}
export async function previewOrganizationBillingMigration(
{ organizationSlug, tier }: OrganizationBillingMigrationPreviewVariables,
signal?: AbortSignal
) {
if (!organizationSlug) throw new Error('organizationSlug is required')
if (!tier) throw new Error('tier is required')
const payload: { tier: string } = {
tier,
}
const response = await post(
`${API_URL}/organizations/${organizationSlug}/org-billing-migration/preview`,
payload,
{ signal }
)
if (response.error) throw response.error
return response as OrganizationBillingMigrationPreviewResponse
}
export type OrganizationBillingMigrationPreviewData = Awaited<
ReturnType<typeof previewOrganizationBillingMigration>
>
export type OrganizationBillingMigrationPreviewError = {
message: string
}
export const useOrganizationBillingMigrationPreview = <
TData = OrganizationBillingMigrationPreviewData
>(
{ organizationSlug, tier }: OrganizationBillingMigrationPreviewVariables,
{
enabled = true,
...options
}: UseQueryOptions<
OrganizationBillingMigrationPreviewData,
OrganizationBillingMigrationPreviewError,
TData
> = {}
) =>
useQuery<
OrganizationBillingMigrationPreviewData,
OrganizationBillingMigrationPreviewError,
TData
>(
organizationKeys.migrateBillingPreview(organizationSlug),
({ signal }) => previewOrganizationBillingMigration({ organizationSlug, tier }, signal),
{
enabled: enabled && typeof organizationSlug !== 'undefined' && typeof tier !== 'undefined',
...options,
retry: (failureCount, error) => {
// Don't retry on 400s
if (
typeof error === 'object' &&
error !== null &&
'code' in error &&
(error as any).code === 400
) {
return false
}
if (failureCount < 3) {
return true
}
return false
},
}
)