mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 12:25:05 +03:00
Add project invoices to project billing page
This commit is contained in:
1 parent
9cdc753bdf
commit
2e7ff9eda8
5 files changed
+183
-6
No files matched your search
@@ -0,0 +1,165 @@
|
||||
import { FC, useState, useEffect } from 'react'
|
||||
import {
|
||||
Button,
|
||||
Loading,
|
||||
Typography,
|
||||
IconFileText,
|
||||
IconDownload,
|
||||
IconChevronLeft,
|
||||
IconChevronRight,
|
||||
} from '@supabase/ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { get, head } from 'lib/common/fetch'
|
||||
import Table from 'components/to-be-cleaned/Table'
|
||||
|
||||
const PAGE_LIMIT = 10
|
||||
|
||||
interface Props {
|
||||
projectRef: string
|
||||
}
|
||||
|
||||
const Invoices: FC<Props> = ({ projectRef }) => {
|
||||
const { ui } = useStore()
|
||||
const [loading, setLoading] = useState<any>(false)
|
||||
|
||||
const [page, setPage] = useState(1)
|
||||
const [count, setCount] = useState(0)
|
||||
const [invoices, setInvoices] = useState<any>([])
|
||||
|
||||
const offset = (page - 1) * PAGE_LIMIT
|
||||
|
||||
useEffect(() => {
|
||||
let cancel = false
|
||||
const page = 1
|
||||
|
||||
const fetchInvoiceCount = async () => {
|
||||
const res = await head(`${API_URL}/projects/${projectRef}/invoices`, ['X-Total-Count'])
|
||||
if (!cancel) {
|
||||
if (res.error) {
|
||||
ui.setNotification({ category: 'error', message: res.error.message })
|
||||
} else {
|
||||
setCount(res['X-Total-Count'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setPage(page)
|
||||
fetchInvoices(page)
|
||||
fetchInvoiceCount()
|
||||
|
||||
return () => {
|
||||
cancel = true
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fetchInvoices = async (page: number) => {
|
||||
setLoading(true)
|
||||
setPage(page)
|
||||
|
||||
const offset = (page - 1) * PAGE_LIMIT
|
||||
const invoices = await get(
|
||||
`${API_URL}/projects/${projectRef}/invoices?offset=${offset}&limit=${PAGE_LIMIT}`
|
||||
)
|
||||
|
||||
if (invoices.error) {
|
||||
ui.setNotification({ category: 'error', message: invoices.error.message })
|
||||
} else {
|
||||
setInvoices(invoices)
|
||||
}
|
||||
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="my-4 container max-w-4xl space-y-1">
|
||||
<Loading active={loading}>
|
||||
<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-download" className="text-right"></Table.th>,
|
||||
]}
|
||||
body={
|
||||
invoices.length === 0 ? (
|
||||
<Table.tr>
|
||||
<Table.td colSpan={5} className="p-3 py-12 text-center">
|
||||
<Typography.Text type="secondary">
|
||||
{loading ? 'Checking for invoices' : 'No invoices for this project yet'}
|
||||
</Typography.Text>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
) : (
|
||||
<>
|
||||
{invoices.map((x: any) => {
|
||||
return (
|
||||
<Table.tr key={x.id}>
|
||||
<Table.td>
|
||||
<IconFileText size="xxl" />
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<Typography.Text>
|
||||
{new Date(x.period_end * 1000).toLocaleString()}
|
||||
</Typography.Text>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<Typography.Text>
|
||||
{x.subtotal >= 0
|
||||
? `$${x.subtotal / 100}`
|
||||
: `-$${Math.abs(x.subtotal / 100)}`}
|
||||
</Typography.Text>
|
||||
</Table.td>
|
||||
<Table.td>
|
||||
<Typography.Text>{x.number}</Typography.Text>
|
||||
</Table.td>
|
||||
<Table.td className="align-right">
|
||||
<div className="flex items-center space-x-2 justify-end">
|
||||
<a href={x.hosted_invoice_url} target="_blank">
|
||||
<Button type="outline">View invoice</Button>
|
||||
</a>
|
||||
<form method="get" action={x.invoice_pdf}>
|
||||
<Button type="outline" icon={<IconDownload />} htmlType="submit" />
|
||||
</form>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
)
|
||||
})}
|
||||
<Table.tr key="navigation">
|
||||
<Table.td colSpan={5}>
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-sm opacity-50">
|
||||
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 () => await fetchInvoices(page - 1)}
|
||||
/>
|
||||
<Button
|
||||
icon={<IconChevronRight />}
|
||||
type="default"
|
||||
size="tiny"
|
||||
disabled={page * PAGE_LIMIT >= count}
|
||||
onClick={async () => await fetchInvoices(page + 1)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Table.td>
|
||||
</Table.tr>
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Loading>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Invoices
|
||||
@@ -56,7 +56,9 @@ const Subscription: FC<Props> = ({
|
||||
<div className="bg-panel-body-light dark:bg-panel-body-dark">
|
||||
<div className="px-6 pt-4 flex items-center justify-between">
|
||||
<div className="flex flex-col">
|
||||
<p className="text-sm">{showProjectName ? project.name : 'Current subscription'}</p>
|
||||
<p className="text-scale-1100 text-sm">
|
||||
{showProjectName ? project.name : 'Current subscription'}
|
||||
</p>
|
||||
<h3 className="text-xl mb-0">{subscription?.tier.name ?? '-'}</h3>
|
||||
</div>
|
||||
{/* <UpgradeButton paid={paid} projectRef={project.ref} /> */}
|
||||
|
||||
@@ -10,6 +10,8 @@ import ComputeSizeSelection from './AddOns/ComputeSizeSelection'
|
||||
import AddNewPaymentMethodModal from './AddNewPaymentMethodModal'
|
||||
import UpdateSuccess from './UpdateSuccess'
|
||||
|
||||
import Invoices from './Invoices'
|
||||
|
||||
export * from './Subscription/Subscription.types'
|
||||
export * from './PAYGUsage/PAYGUsage.constants'
|
||||
export {
|
||||
@@ -23,4 +25,5 @@ export {
|
||||
ComputeSizeSelection,
|
||||
AddNewPaymentMethodModal,
|
||||
UpdateSuccess,
|
||||
Invoices,
|
||||
}
|
||||
@@ -16,6 +16,10 @@ import Table from 'components/to-be-cleaned/Table'
|
||||
|
||||
const PAGE_LIMIT = 10
|
||||
|
||||
/**
|
||||
* Eventually deprecate this - as we move on to show invoices by project on the organization billing page
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
organization: any
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import dayjs from 'dayjs'
|
||||
import { FC, useEffect, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Typography, Loading, IconArrowRight, Button } from '@supabase/ui'
|
||||
import { Typography, Loading, IconArrowRight } from '@supabase/ui'
|
||||
import { get as _get, maxBy } from 'lodash'
|
||||
import { Dictionary } from '@supabase/grid'
|
||||
|
||||
import { API_URL } from 'lib/constants'
|
||||
import { useStore, useSubscriptionStats, withAuth } from 'hooks'
|
||||
import { useStore, withAuth } from 'hooks'
|
||||
import { post, get } from 'lib/common/fetch'
|
||||
import {
|
||||
API_URL,
|
||||
DATE_FORMAT,
|
||||
STRIPE_PRODUCT_IDS,
|
||||
TIME_PERIODS_REPORTS,
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
Subscription,
|
||||
StripeSubscription,
|
||||
chargeableProducts,
|
||||
Invoices,
|
||||
} from 'components/interfaces/Billing'
|
||||
|
||||
import ProjectUsage from 'components/to-be-cleaned/Usage'
|
||||
@@ -51,8 +52,6 @@ const Settings: FC<SettingsProps> = ({ project }) => {
|
||||
const { ui } = useStore()
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
|
||||
const subscriptionStats = useSubscriptionStats()
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [subscription, setSubscription] = useState<StripeSubscription>()
|
||||
const [paygStats, setPaygStats] = useState<Dictionary<number>>()
|
||||
@@ -163,6 +162,10 @@ const Settings: FC<SettingsProps> = ({ project }) => {
|
||||
) : (
|
||||
<ProjectUsage projectRef={project.ref} subscription_id={project.subscription_id} />
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<h4 className="text-lg">Invoices</h4>
|
||||
<Invoices projectRef={ui.selectedProject?.ref ?? ''} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user