11import { supabaseAdmin } from "@changespage/supabase/admin" ;
22import { SpinnerWithSpacing } from "@changespage/ui" ;
33import { DateTime } from "@changespage/utils" ;
4- import { CurrencyDollarIcon , DatabaseIcon } from "@heroicons/react/outline" ;
4+ import {
5+ CurrencyDollarIcon ,
6+ DatabaseIcon ,
7+ EyeIcon ,
8+ } from "@heroicons/react/outline" ;
59import { CalendarIcon } from "@heroicons/react/solid" ;
610import classNames from "classnames" ;
711import { InferGetServerSidePropsType } from "next" ;
@@ -15,11 +19,12 @@ import { httpPost } from "../../utils/http";
1519import { withSupabase } from "../../utils/supabase/withSupabase" ;
1620import { useUserData } from "../../utils/useUser" ;
1721
18- interface PageStorageUsage {
22+ interface PageUsageStats {
1923 page_id : string ;
2024 page_title : string ;
2125 total_bytes : number ;
2226 total_pretty : string ;
27+ page_views_30d : number ;
2328}
2429
2530function formatBytes ( bytes : number ) : string {
@@ -39,22 +44,31 @@ export const getServerSideProps = withSupabase(async (_, { user }) => {
3944 if ( ! pages || pages . length === 0 ) {
4045 return {
4146 props : {
42- storageUsage : [ ] ,
47+ usageStats : [ ] ,
4348 } ,
4449 } ;
4550 }
4651
47- const storageUsage : PageStorageUsage [ ] = await Promise . all (
52+ const thirtyDaysAgo = new Date ( Date . now ( ) - 30 * 24 * 60 * 60 * 1000 ) ;
53+
54+ const usageStats : PageUsageStats [ ] = await Promise . all (
4855 pages . map ( async ( page ) => {
49- const { data : objects } = await supabaseAdmin
50- // @ts -expect-error - storage schema not in Database types
51- . schema ( "storage" )
52- . from ( "objects" )
53- . select ( "metadata" )
54- . eq ( "bucket_id" , "images" )
55- . like ( "name" , `${ user . id } /${ page . id } /%` ) ;
56+ const [ storageResult , viewsResult ] = await Promise . all ( [
57+ supabaseAdmin
58+ // @ts -expect-error - storage schema not in Database types
59+ . schema ( "storage" )
60+ . from ( "objects" )
61+ . select ( "metadata" )
62+ . eq ( "bucket_id" , "images" )
63+ . like ( "name" , `${ user . id } /${ page . id } /%` ) ,
64+ supabaseAdmin
65+ . from ( "page_views" )
66+ . select ( "id" , { count : "exact" , head : true } )
67+ . eq ( "page_id" , page . id )
68+ . gte ( "created_at" , thirtyDaysAgo . toISOString ( ) ) ,
69+ ] ) ;
5670
57- const totalBytes = ( objects || [ ] ) . reduce ( ( sum , obj ) => {
71+ const totalBytes = ( storageResult . data || [ ] ) . reduce ( ( sum , obj ) => {
5872 const size = ( obj . metadata as { size ?: number } ) ?. size || 0 ;
5973 return sum + size ;
6074 } , 0 ) ;
@@ -64,19 +78,20 @@ export const getServerSideProps = withSupabase(async (_, { user }) => {
6478 page_title : page . title ,
6579 total_bytes : totalBytes ,
6680 total_pretty : formatBytes ( totalBytes ) ,
81+ page_views_30d : viewsResult . count || 0 ,
6782 } ;
6883 } )
6984 ) ;
7085
7186 return {
7287 props : {
73- storageUsage ,
88+ usageStats ,
7489 } ,
7590 } ;
7691} ) ;
7792
7893export default function Billing ( {
79- storageUsage ,
94+ usageStats ,
8095} : InferGetServerSidePropsType < typeof getServerSideProps > ) {
8196 const { billingDetails, fetchBilling } = useUserData ( ) ;
8297
@@ -250,15 +265,15 @@ export default function Billing({
250265 < div className = "md:col-span-1" >
251266 < div className = "px-4 sm:px-0" >
252267 < h3 className = "text-lg font-medium leading-6 text-gray-900 dark:text-gray-50" >
253- Storage Usage
268+ Usage
254269 </ h3 >
255270 < p className = "mt-1 text-sm text-gray-600 dark:text-gray-400" >
256- Storage used by each page for images and uploads .
271+ Storage and page views (last 30 days) for each page .
257272 </ p >
258273 </ div >
259274 </ div >
260275 < div className = "mt-5 md:mt-0 md:col-span-2" >
261- { storageUsage . length === 0 ? (
276+ { usageStats . length === 0 ? (
262277 < div className = "shadow overflow-hidden sm:rounded-md" >
263278 < div className = "px-4 py-3 bg-white dark:bg-black sm:p-3" >
264279 < p className = "text-sm text-gray-500 dark:text-gray-400" >
@@ -270,18 +285,20 @@ export default function Billing({
270285 < div className = "shadow overflow-hidden sm:rounded-md" >
271286 < div className = "bg-white dark:bg-black" >
272287 < ul className = "divide-y divide-gray-200 dark:divide-gray-800" >
273- { storageUsage . map ( ( page ) => (
288+ { usageStats . map ( ( page ) => (
274289 < li key = { page . page_id } className = "px-4 py-4" >
275- < div className = "flex items-center justify-between" >
290+ < p className = "text-sm font-medium text-gray-900 dark:text-gray-100 mb-2" >
291+ { page . page_title }
292+ </ p >
293+ < div className = "flex items-center gap-6 text-sm text-gray-500 dark:text-gray-400" >
276294 < div className = "flex items-center" >
277- < DatabaseIcon className = "h-5 w-5 text-gray-400 mr-3" />
278- < p className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
279- { page . page_title }
280- </ p >
281- </ div >
282- < p className = "text-sm text-gray-500 dark:text-gray-400" >
295+ < DatabaseIcon className = "h-4 w-4 mr-1.5" />
283296 { page . total_pretty }
284- </ p >
297+ </ div >
298+ < div className = "flex items-center" >
299+ < EyeIcon className = "h-4 w-4 mr-1.5" />
300+ { page . page_views_30d . toLocaleString ( ) } views
301+ </ div >
285302 </ div >
286303 </ li >
287304 ) ) }
0 commit comments