33import { useEffect , useMemo , useRef , useState } from 'react'
44import { Download , Search , Trash2 } from 'lucide-react'
55import { useParams } from 'next/navigation'
6- import { Input } from '@/components/ui'
6+ import { Input , Progress , Skeleton } from '@/components/ui'
77import { Button } from '@/components/ui/button'
88import {
99 Table ,
@@ -16,6 +16,7 @@ import {
1616import { createLogger } from '@/lib/logs/console/logger'
1717import { getFileExtension } from '@/lib/uploads/file-utils'
1818import type { WorkspaceFileRecord } from '@/lib/uploads/workspace-files'
19+ import { cn } from '@/lib/utils'
1920import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
2021import { useUserPermissions } from '@/hooks/use-user-permissions'
2122import { useWorkspacePermissions } from '@/hooks/use-workspace-permissions'
@@ -38,6 +39,17 @@ const SUPPORTED_EXTENSIONS = [
3839] as const
3940const ACCEPT_ATTR = '.pdf,.csv,.doc,.docx,.txt,.md,.xlsx,.xls,.html,.htm,.pptx,.ppt'
4041
42+ interface StorageInfo {
43+ usedBytes : number
44+ limitBytes : number
45+ percentUsed : number
46+ }
47+
48+ interface UsageData {
49+ plan : string
50+ storage : StorageInfo
51+ }
52+
4153export function FileUploads ( ) {
4254 const params = useParams ( )
4355 const workspaceId = params ?. workspaceId as string
@@ -48,6 +60,9 @@ export function FileUploads() {
4860 const [ uploadError , setUploadError ] = useState < string | null > ( null )
4961 const [ uploadProgress , setUploadProgress ] = useState ( { completed : 0 , total : 0 } )
5062 const fileInputRef = useRef < HTMLInputElement > ( null )
63+ const [ storageInfo , setStorageInfo ] = useState < StorageInfo | null > ( null )
64+ const [ planName , setPlanName ] = useState < string > ( 'free' )
65+ const [ storageLoading , setStorageLoading ] = useState ( true )
5166
5267 const { permissions : workspacePermissions , loading : permissionsLoading } =
5368 useWorkspacePermissions ( workspaceId )
@@ -71,8 +86,28 @@ export function FileUploads() {
7186 }
7287 }
7388
89+ const loadStorageInfo = async ( ) => {
90+ try {
91+ setStorageLoading ( true )
92+ const response = await fetch ( '/api/users/me/usage-limits' )
93+ const data = await response . json ( )
94+
95+ if ( data . success && data . storage ) {
96+ setStorageInfo ( data . storage )
97+ if ( data . usage ?. plan ) {
98+ setPlanName ( data . usage . plan )
99+ }
100+ }
101+ } catch ( error ) {
102+ logger . error ( 'Error loading storage info:' , error )
103+ } finally {
104+ setStorageLoading ( false )
105+ }
106+ }
107+
74108 useEffect ( ( ) => {
75109 void loadFiles ( )
110+ void loadStorageInfo ( )
76111 } , [ workspaceId ] )
77112
78113 const handleUploadClick = ( ) => {
@@ -123,6 +158,7 @@ export function FileUploads() {
123158 }
124159
125160 await loadFiles ( )
161+ await loadStorageInfo ( )
126162 if ( unsupported . length ) {
127163 lastError = `Unsupported file type: ${ unsupported . join ( ', ' ) } `
128164 }
@@ -171,6 +207,7 @@ export function FileUploads() {
171207
172208 if ( data . success ) {
173209 await loadFiles ( )
210+ await loadStorageInfo ( )
174211 }
175212 } catch ( error ) {
176213 logger . error ( 'Error deleting file:' , error )
@@ -206,6 +243,25 @@ export function FileUploads() {
206243 return `${ text . slice ( 0 , start ) } ...${ text . slice ( - end ) } `
207244 }
208245
246+ const formatStorageSize = ( bytes : number ) : string => {
247+ if ( bytes < 1024 ) return `${ bytes } B`
248+ if ( bytes < 1024 * 1024 ) return `${ ( bytes / 1024 ) . toFixed ( 1 ) } KB`
249+ if ( bytes < 1024 * 1024 * 1024 ) return `${ ( bytes / ( 1024 * 1024 ) ) . toFixed ( 1 ) } MB`
250+ return `${ ( bytes / ( 1024 * 1024 * 1024 ) ) . toFixed ( 2 ) } GB`
251+ }
252+
253+ const PLAN_NAMES = {
254+ enterprise : 'Enterprise' ,
255+ team : 'Team' ,
256+ pro : 'Pro' ,
257+ free : 'Free' ,
258+ } as const
259+
260+ const displayPlanName = PLAN_NAMES [ planName as keyof typeof PLAN_NAMES ] || 'Free'
261+
262+ const GRADIENT_TEXT_STYLES =
263+ 'gradient-text bg-gradient-to-b from-gradient-primary via-gradient-secondary to-gradient-primary'
264+
209265 return (
210266 < div className = 'relative flex h-full flex-col' >
211267 { /* Header: search left, file count + Upload right */ }
@@ -220,9 +276,31 @@ export function FileUploads() {
220276 />
221277 </ div >
222278 < div className = 'flex items-center gap-3' >
223- < div className = 'text-muted-foreground text-sm' >
224- { files . length } { files . length === 1 ? 'file' : 'files' }
225- </ div >
279+ { storageLoading ? (
280+ < Skeleton className = 'h-4 w-32' />
281+ ) : storageInfo ? (
282+ < div className = 'flex flex-col items-end gap-1' >
283+ < div className = 'flex items-center gap-2 text-sm' >
284+ < span
285+ className = { cn (
286+ 'font-medium' ,
287+ planName === 'free' ? 'text-foreground' : GRADIENT_TEXT_STYLES
288+ ) }
289+ >
290+ { displayPlanName }
291+ </ span >
292+ < span className = 'text-muted-foreground tabular-nums' >
293+ { formatStorageSize ( storageInfo . usedBytes ) } /{ ' ' }
294+ { formatStorageSize ( storageInfo . limitBytes ) }
295+ </ span >
296+ </ div >
297+ < Progress
298+ value = { Math . min ( storageInfo . percentUsed , 100 ) }
299+ className = 'h-1 w-full'
300+ indicatorClassName = 'bg-black dark:bg-white'
301+ />
302+ </ div >
303+ ) : null }
226304 { userPermissions . canEdit && (
227305 < div className = 'flex items-center' >
228306 < input
@@ -265,7 +343,7 @@ export function FileUploads() {
265343 ) : (
266344 < Table className = 'table-auto text-[13px]' >
267345 < TableHeader >
268- < TableRow >
346+ < TableRow className = 'hover:bg-transparent' >
269347 < TableHead className = 'w-[56%] px-3 text-xs' > Name</ TableHead >
270348 < TableHead className = 'w-[14%] px-3 text-left text-xs' > Size</ TableHead >
271349 < TableHead className = 'w-[15%] px-3 text-left text-xs' > Uploaded</ TableHead >
0 commit comments