ComputeMetric.tsx136 lines · main
1import { ChevronRight } from 'lucide-react'
2import Link from 'next/link'
3import { useMemo } from 'react'
4import { HoverCard, HoverCardContent, HoverCardTrigger } from 'ui'
5
6import { formatUsage } from '../helpers'
7import { Metric } from './BillingBreakdown.constants'
8import { ComputeUsageMetric, PricingMetric } from '@/data/analytics/org-daily-stats-query'
9import type { OrgUsageResponse } from '@/data/usage/org-usage-query'
10import { DOCS_URL } from '@/lib/constants'
11import { formatCurrency } from '@/lib/helpers'
12
13export interface ComputeMetricProps {
14 slug?: string
15 metric: Metric
16 usage: OrgUsageResponse
17 relativeToSubscription: boolean
18 className?: string
19}
20
21export const ComputeMetric = ({
22 slug,
23 metric,
24 usage,
25 relativeToSubscription,
26 className,
27}: ComputeMetricProps) => {
28 const usageMeta = usage.usages.find((x) => x.metric === metric.key)
29
30 const usageLabel = useMemo(() => {
31 if (usageMeta?.pricing_free_units) {
32 return `${usageMeta?.usage?.toLocaleString() ?? 0} / ${
33 usageMeta?.pricing_free_units ?? 0
34 } hours`
35 } else {
36 return `${usageMeta?.usage?.toLocaleString() ?? 0} hours`
37 }
38 }, [usageMeta])
39
40 const sortedProjectAllocations = useMemo(() => {
41 if (!usageMeta || !usageMeta.project_allocations) return []
42
43 return usageMeta.project_allocations.sort((a, b) => b.usage - a.usage)
44 }, [usageMeta])
45
46 return (
47 <HoverCard openDelay={50} closeDelay={200}>
48 <HoverCardTrigger asChild>
49 <div className={className}>
50 <Link href={`/org/${slug}/usage#${metric.anchor}`}>
51 <div className="group flex items-center space-x-2">
52 <p className="text-sm text-foreground-light group-hover:text-foreground transition cursor-pointer">
53 {metric.name}
54 </p>
55 <ChevronRight strokeWidth={1.5} size={16} className="transition" />
56 </div>
57 </Link>
58 <span className="text-sm">{usageLabel}</span>&nbsp;
59 {relativeToSubscription && usageMeta?.cost && usageMeta.cost > 0 ? (
60 <span className="text-sm" translate="no">
61 ({formatCurrency(usageMeta?.cost)})
62 </span>
63 ) : null}
64 </div>
65 </HoverCardTrigger>
66 <HoverCardContent side="bottom" align="end" className="w-[500px]" animate="slide-in">
67 <div className="text-sm text-foreground space-y-2">
68 <p className="font-medium" translate="no">
69 {usageMeta?.unit_price_desc}
70 </p>
71
72 <div className="my-2">
73 {usageMeta?.metric === ComputeUsageMetric.COMPUTE_HOURS_BRANCH ? (
74 <p className="text-sm">
75 Each Preview branch is a separate environment with all Briven services (Database,
76 Auth, Storage, etc.).{' '}
77 <Link
78 href={`${DOCS_URL}/guides/platform/manage-your-usage/branching`}
79 target="_blank"
80 className="transition text-brand hover:text-brand-600 underline"
81 >
82 Read more
83 </Link>
84 </p>
85 ) : (
86 <p className="text-sm">
87 Every project is a dedicated server and database. For every hour your project is
88 active, it incurs compute costs based on the compute size of your project. Paused
89 projects do not incur compute costs.{' '}
90 <Link
91 href={`${DOCS_URL}/guides/platform/manage-your-usage/compute`}
92 target="_blank"
93 className="transition text-brand hover:text-brand-600 underline"
94 >
95 Read more
96 </Link>
97 </p>
98 )}
99 </div>
100
101 {usageMeta && sortedProjectAllocations.length > 0 && (
102 <table className="list-disc w-full">
103 <thead>
104 <tr>
105 <th className="text-left">Project</th>
106 <th className="text-right">Usage</th>
107 </tr>
108 </thead>
109 <tbody>
110 {sortedProjectAllocations.map((allocation) => (
111 <tr key={`${usageMeta.metric}_${allocation.ref}`}>
112 <td>{allocation.name}</td>
113 <td className="text-right">
114 {formatUsage(usageMeta.metric as PricingMetric, allocation)}
115 </td>
116 </tr>
117 ))}
118 <tr></tr>
119 </tbody>
120 <tfoot>
121 <tr>
122 <td className="py-2 border-t text-left">Total (Hours)</td>
123 <td className="py-2 border-t text-right">
124 {formatUsage(usageMeta.metric as PricingMetric, {
125 usage: usageMeta.usage_original,
126 })}
127 </td>
128 </tr>
129 </tfoot>
130 </table>
131 )}
132 </div>
133 </HoverCardContent>
134 </HoverCard>
135 )
136}