Skip to content

Commit fb3d6d4

Browse files
authored
feat(files): added usage indicator for file storage to settings (#1736)
* feat(files): added usage indicator for file storage to settings * cleanup
1 parent ec2cc82 commit fb3d6d4

File tree

2 files changed

+92
-6
lines changed
  • apps/sim/app
    • api/users/me/usage-limits
    • workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/file-uploads

2 files changed

+92
-6
lines changed

apps/sim/app/api/users/me/usage-limits/route.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { checkHybridAuth } from '@/lib/auth/hybrid'
33
import { checkServerSideUsageLimits } from '@/lib/billing'
44
import { getHighestPrioritySubscription } from '@/lib/billing/core/subscription'
55
import { getEffectiveCurrentPeriodCost } from '@/lib/billing/core/usage'
6+
import { getUserStorageLimit, getUserStorageUsage } from '@/lib/billing/storage'
67
import { createLogger } from '@/lib/logs/console/logger'
78
import { createErrorResponse } from '@/app/api/workflows/utils'
89
import { RateLimiter } from '@/services/queue'
@@ -37,9 +38,11 @@ export async function GET(request: NextRequest) {
3738
])
3839

3940
// Usage summary (current period cost + limit + plan)
40-
const [usageCheck, effectiveCost] = await Promise.all([
41+
const [usageCheck, effectiveCost, storageUsage, storageLimit] = await Promise.all([
4142
checkServerSideUsageLimits(authenticatedUserId),
4243
getEffectiveCurrentPeriodCost(authenticatedUserId),
44+
getUserStorageUsage(authenticatedUserId),
45+
getUserStorageLimit(authenticatedUserId),
4346
])
4447

4548
const currentPeriodCost = effectiveCost
@@ -66,6 +69,11 @@ export async function GET(request: NextRequest) {
6669
limit: usageCheck.limit,
6770
plan: userSubscription?.plan || 'free',
6871
},
72+
storage: {
73+
usedBytes: storageUsage,
74+
limitBytes: storageLimit,
75+
percentUsed: storageLimit > 0 ? (storageUsage / storageLimit) * 100 : 0,
76+
},
6977
})
7078
} catch (error: any) {
7179
logger.error('Error checking usage limits:', error)

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/file-uploads/file-uploads.tsx

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useEffect, useMemo, useRef, useState } from 'react'
44
import { Download, Search, Trash2 } from 'lucide-react'
55
import { useParams } from 'next/navigation'
6-
import { Input } from '@/components/ui'
6+
import { Input, Progress, Skeleton } from '@/components/ui'
77
import { Button } from '@/components/ui/button'
88
import {
99
Table,
@@ -16,6 +16,7 @@ import {
1616
import { createLogger } from '@/lib/logs/console/logger'
1717
import { getFileExtension } from '@/lib/uploads/file-utils'
1818
import type { WorkspaceFileRecord } from '@/lib/uploads/workspace-files'
19+
import { cn } from '@/lib/utils'
1920
import { getDocumentIcon } from '@/app/workspace/[workspaceId]/knowledge/components'
2021
import { useUserPermissions } from '@/hooks/use-user-permissions'
2122
import { useWorkspacePermissions } from '@/hooks/use-workspace-permissions'
@@ -38,6 +39,17 @@ const SUPPORTED_EXTENSIONS = [
3839
] as const
3940
const 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+
4153
export 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

Comments
 (0)