template-saas/apps/frontend/src/components/storage/StorageUsageCard.tsx
Adrian Flores Cortes f853c49568
Some checks are pending
CI / Backend CI (push) Waiting to run
CI / Frontend CI (push) Waiting to run
CI / Security Scan (push) Waiting to run
CI / CI Summary (push) Blocked by required conditions
feat(frontend): Align documentation with development - complete frontend audit
- Add Sales/Commissions routes to router (11 new routes)
- Complete authStore (refreshTokens, updateProfile methods)
- Add JSDoc documentation to 40+ components across all categories
- Create FRONTEND-ROUTING.md with complete route mapping
- Create FRONTEND-PAGES-SPEC.md with 38 page specifications
- Update FRONTEND_INVENTORY.yml to v4.1.0 with resolved gaps

Components documented: ai/, audit/, commissions/, feature-flags/,
notifications/, sales/, storage/, webhooks/, whatsapp/

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:23:50 -06:00

132 lines
4.4 KiB
TypeScript

import { HardDrive, Folder } from 'lucide-react';
import { useStorageUsage } from '@/hooks/useStorage';
/**
* Props for the StorageUsageCard component.
*/
interface StorageUsageCardProps {
/** Additional CSS classes to apply to the card container */
className?: string;
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
/**
* Displays storage usage statistics in a card format.
* Shows total bytes used, usage percentage with color-coded progress bar,
* file count, max file size, and breakdown by folder. Handles loading
* and error states gracefully.
*
* @description Renders a card showing storage quota usage and file statistics.
* @param props - The component props
* @param props.className - Additional CSS classes for the card container
*
* @example
* // Basic usage
* <StorageUsageCard />
*
* @example
* // With custom styling
* <StorageUsageCard className="col-span-2 shadow-lg" />
*/
export function StorageUsageCard({ className = '' }: StorageUsageCardProps) {
const { data, isLoading, error } = useStorageUsage();
if (isLoading) {
return (
<div className={`bg-white rounded-lg border p-6 ${className}`}>
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-1/3 mb-4" />
<div className="h-8 bg-gray-200 rounded w-1/2 mb-4" />
<div className="h-2 bg-gray-200 rounded w-full" />
</div>
</div>
);
}
if (error || !data) {
return (
<div className={`bg-white rounded-lg border p-6 ${className}`}>
<p className="text-gray-500">Unable to load storage usage</p>
</div>
);
}
const usedPercent = data.maxBytes ? data.usagePercent : 0;
const progressColor =
usedPercent > 90 ? 'bg-red-500' : usedPercent > 70 ? 'bg-yellow-500' : 'bg-blue-500';
return (
<div className={`bg-white rounded-lg border p-6 ${className}`}>
<div className="flex items-center gap-3 mb-4">
<div className="p-2 bg-blue-100 rounded-lg">
<HardDrive className="w-5 h-5 text-blue-600" />
</div>
<h3 className="font-semibold text-gray-900">Storage Usage</h3>
</div>
{/* Usage stats */}
<div className="space-y-4">
<div>
<div className="flex justify-between text-sm mb-1">
<span className="text-gray-600">
{formatBytes(data.totalBytes)} used
</span>
{data.maxBytes && (
<span className="text-gray-500">
of {formatBytes(data.maxBytes)}
</span>
)}
</div>
{data.maxBytes && (
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className={`${progressColor} h-2 rounded-full transition-all duration-300`}
style={{ width: `${Math.min(usedPercent, 100)}%` }}
/>
</div>
)}
</div>
{/* Files count */}
<div className="flex justify-between text-sm">
<span className="text-gray-600">Total files</span>
<span className="font-medium">{data.totalFiles.toLocaleString()}</span>
</div>
{/* Max file size */}
{data.maxFileSize && (
<div className="flex justify-between text-sm">
<span className="text-gray-600">Max file size</span>
<span className="font-medium">{formatBytes(data.maxFileSize)}</span>
</div>
)}
{/* Files by folder */}
{Object.keys(data.filesByFolder).length > 0 && (
<div className="pt-4 border-t">
<h4 className="text-sm font-medium text-gray-900 mb-2">By Folder</h4>
<div className="space-y-2">
{Object.entries(data.filesByFolder).map(([folder, count]) => (
<div key={folder} className="flex items-center justify-between text-sm">
<div className="flex items-center gap-2 text-gray-600">
<Folder className="w-4 h-4" />
<span>{folder}</span>
</div>
<span className="font-medium">{count}</span>
</div>
))}
</div>
</div>
)}
</div>
</div>
);
}