- Deleted migration file that removed cached profile fields from the users table, centralizing profile data retrieval from WHMCS. - Updated CsrfMiddleware to include new public authentication endpoints for password reset, setting password, and WHMCS account linking. - Enhanced error handling in password and WHMCS linking workflows to provide clearer feedback on missing mappings and improve user experience. - Adjusted user creation and update methods in UsersFacade to handle cases where WHMCS mappings are not yet available, ensuring smoother account setup.
258 lines
9.3 KiB
TypeScript
258 lines
9.3 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { ChartBarIcon, ExclamationTriangleIcon } from "@heroicons/react/24/outline";
|
|
|
|
export interface SimUsage {
|
|
account: string;
|
|
todayUsageKb: number;
|
|
todayUsageMb: number;
|
|
recentDaysUsage: Array<{
|
|
date: string;
|
|
usageKb: number;
|
|
usageMb: number;
|
|
}>;
|
|
isBlacklisted: boolean;
|
|
}
|
|
|
|
interface DataUsageChartProps {
|
|
usage: SimUsage;
|
|
remainingQuotaMb: number;
|
|
isLoading?: boolean;
|
|
error?: string | null;
|
|
embedded?: boolean; // when true, render content without card container
|
|
}
|
|
|
|
export function DataUsageChart({
|
|
usage,
|
|
remainingQuotaMb,
|
|
isLoading,
|
|
error,
|
|
embedded = false,
|
|
}: DataUsageChartProps) {
|
|
const formatUsage = (usageMb: number) => {
|
|
if (usageMb >= 1000) {
|
|
return `${(usageMb / 1000).toFixed(1)} GB`;
|
|
}
|
|
return `${usageMb.toFixed(0)} MB`;
|
|
};
|
|
|
|
const getUsageColor = (percentage: number) => {
|
|
if (percentage >= 90) return "bg-red-500";
|
|
if (percentage >= 75) return "bg-yellow-500";
|
|
if (percentage >= 50) return "bg-orange-500";
|
|
return "bg-green-500";
|
|
};
|
|
|
|
const getUsageTextColor = (percentage: number) => {
|
|
if (percentage >= 90) return "text-red-600";
|
|
if (percentage >= 75) return "text-yellow-600";
|
|
if (percentage >= 50) return "text-orange-600";
|
|
return "text-green-600";
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
|
<div className="animate-pulse">
|
|
<div className="h-6 bg-gray-200 rounded w-1/3 mb-4"></div>
|
|
<div className="h-4 bg-gray-200 rounded w-full mb-2"></div>
|
|
<div className="h-8 bg-gray-200 rounded mb-4"></div>
|
|
<div className="space-y-2">
|
|
<div className="h-4 bg-gray-200 rounded w-3/4"></div>
|
|
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className={`${embedded ? "" : "bg-white shadow rounded-lg "}p-6`}>
|
|
<div className="text-center">
|
|
<ExclamationTriangleIcon className="h-12 w-12 text-red-400 mx-auto mb-4" />
|
|
<h3 className="text-lg font-medium text-gray-900 mb-2">Error Loading Usage Data</h3>
|
|
<p className="text-red-600">{error}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Calculate total usage from recent days (assume it includes today)
|
|
const totalRecentUsage =
|
|
usage.recentDaysUsage.reduce((sum, day) => sum + day.usageMb, 0) + usage.todayUsageMb;
|
|
const totalQuota = remainingQuotaMb + totalRecentUsage;
|
|
const usagePercentage = totalQuota > 0 ? (totalRecentUsage / totalQuota) * 100 : 0;
|
|
|
|
return (
|
|
<div
|
|
className={`${embedded ? "" : "bg-white shadow-lg rounded-xl border border-gray-100 hover:shadow-xl transition-shadow duration-300"}`}
|
|
>
|
|
{/* Header */}
|
|
<div className={`${embedded ? "" : "px-6 lg:px-8 py-5 border-b border-gray-200"}`}>
|
|
<div className="flex items-center">
|
|
<div className="bg-blue-50 rounded-xl p-2 mr-4">
|
|
<ChartBarIcon className="h-6 w-6 text-blue-600" />
|
|
</div>
|
|
<div>
|
|
<h3 className="text-xl font-semibold text-gray-900">Data Usage</h3>
|
|
<p className="text-sm text-gray-600">Current month usage and remaining quota</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className={`${embedded ? "" : "px-6 lg:px-8 py-6"}`}>
|
|
{/* Current Usage Overview */}
|
|
<div className="mb-6">
|
|
<div className="flex justify-between items-center mb-2">
|
|
<span className="text-sm font-medium text-gray-700">Used this month</span>
|
|
<span className={`text-sm font-semibold ${getUsageTextColor(usagePercentage)}`}>
|
|
{formatUsage(totalRecentUsage)} of {formatUsage(totalQuota)}
|
|
</span>
|
|
</div>
|
|
|
|
{/* Progress Bar */}
|
|
<div className="w-full bg-gray-200 rounded-full h-3">
|
|
<div
|
|
className={`h-3 rounded-full transition-all duration-300 ${getUsageColor(usagePercentage)}`}
|
|
style={{ width: `${Math.min(usagePercentage, 100)}%` }}
|
|
></div>
|
|
</div>
|
|
|
|
<div className="flex justify-between text-xs text-gray-500 mt-1">
|
|
<span>0%</span>
|
|
<span className={getUsageTextColor(usagePercentage)}>
|
|
{usagePercentage.toFixed(1)}% used
|
|
</span>
|
|
<span>100%</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Today's Usage */}
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-8">
|
|
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-xl p-6 border border-blue-200">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<div className="text-3xl font-bold text-blue-600">
|
|
{formatUsage(usage.todayUsageMb)}
|
|
</div>
|
|
<div className="text-sm font-medium text-blue-700 mt-1">Used today</div>
|
|
</div>
|
|
<div className="bg-blue-200 rounded-full p-3">
|
|
<svg
|
|
className="h-6 w-6 text-blue-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-xl p-6 border border-green-200">
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<div className="text-3xl font-bold text-green-600">
|
|
{formatUsage(remainingQuotaMb)}
|
|
</div>
|
|
<div className="text-sm font-medium text-green-700 mt-1">Remaining</div>
|
|
</div>
|
|
<div className="bg-green-200 rounded-full p-3">
|
|
<svg
|
|
className="h-6 w-6 text-green-600"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
viewBox="0 0 24 24"
|
|
>
|
|
<path
|
|
strokeLinecap="round"
|
|
strokeLinejoin="round"
|
|
strokeWidth={2}
|
|
d="M20 12H4m16 0l-4 4m4-4l-4-4"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Recent Days Usage */}
|
|
{usage.recentDaysUsage.length > 0 && (
|
|
<div>
|
|
<h4 className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-3">
|
|
Recent Usage History
|
|
</h4>
|
|
<div className="space-y-2">
|
|
{usage.recentDaysUsage.slice(0, 5).map((day, index) => {
|
|
const dayPercentage = totalQuota > 0 ? (day.usageMb / totalQuota) * 100 : 0;
|
|
return (
|
|
<div key={index} className="flex items-center justify-between py-2">
|
|
<span className="text-sm text-gray-600">
|
|
{new Date(day.date).toLocaleDateString("en-US", {
|
|
month: "short",
|
|
day: "numeric",
|
|
})}
|
|
</span>
|
|
<div className="flex items-center space-x-3">
|
|
<div className="w-24 bg-gray-200 rounded-full h-2">
|
|
<div
|
|
className="bg-blue-500 h-2 rounded-full transition-all duration-300"
|
|
style={{ width: `${Math.min(dayPercentage, 100)}%` }}
|
|
></div>
|
|
</div>
|
|
<span className="text-sm font-medium text-gray-900 w-16 text-right">
|
|
{formatUsage(day.usageMb)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Warnings */}
|
|
|
|
{usagePercentage >= 90 && (
|
|
<div className="mt-6 bg-red-50 border border-red-200 rounded-lg p-4">
|
|
<div className="flex items-center">
|
|
<ExclamationTriangleIcon className="h-5 w-5 text-red-500 mr-2" />
|
|
<div>
|
|
<h4 className="text-sm font-medium text-red-800">High Usage Warning</h4>
|
|
<p className="text-sm text-red-700 mt-1">
|
|
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider topping
|
|
up to avoid service interruption.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{usagePercentage >= 75 && usagePercentage < 90 && (
|
|
<div className="mt-6 bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
|
<div className="flex items-center">
|
|
<ExclamationTriangleIcon className="h-5 w-5 text-yellow-500 mr-2" />
|
|
<div>
|
|
<h4 className="text-sm font-medium text-yellow-800">Usage Notice</h4>
|
|
<p className="text-sm text-yellow-700 mt-1">
|
|
You have used {usagePercentage.toFixed(1)}% of your data quota. Consider
|
|
monitoring your usage.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|