Enhance billing and subscription management: Added payment method checks in ProtectedRoute, improved error handling in billing components, and optimized API calls to reduce throttling. Updated user account handling in various components to ensure accurate plan and subscription data display.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Search, Filter, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
@@ -26,6 +27,7 @@ export default function AdminAllAccountsPage() {
|
||||
const [error, setError] = useState<string>('');
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
loadAccounts();
|
||||
@@ -172,7 +174,10 @@ export default function AdminAllAccountsPage() {
|
||||
{new Date(account.created_at).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button className="text-blue-600 hover:text-blue-700 text-sm font-medium">
|
||||
<button
|
||||
className="text-blue-600 hover:text-blue-700 text-sm font-medium"
|
||||
onClick={() => navigate(`/admin/subscriptions?account_id=${account.id}`)}
|
||||
>
|
||||
Manage
|
||||
</button>
|
||||
</td>
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
*/
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Search, Filter, Loader2, AlertCircle } from 'lucide-react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Search, Filter, Loader2, AlertCircle, Check, X, RefreshCw } from 'lucide-react';
|
||||
import { Card } from '../../components/ui/card';
|
||||
import Badge from '../../components/ui/badge/Badge';
|
||||
import { fetchAPI } from '../../services/api';
|
||||
import Button from '../../components/ui/button/Button';
|
||||
|
||||
interface Subscription {
|
||||
id: number;
|
||||
@@ -17,6 +19,8 @@ interface Subscription {
|
||||
current_period_end: string;
|
||||
cancel_at_period_end: boolean;
|
||||
plan_name: string;
|
||||
account: number;
|
||||
plan: number | string;
|
||||
}
|
||||
|
||||
export default function AdminSubscriptionsPage() {
|
||||
@@ -24,15 +28,20 @@ export default function AdminSubscriptionsPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string>('');
|
||||
const [statusFilter, setStatusFilter] = useState('all');
|
||||
const [actionLoadingId, setActionLoadingId] = useState<number | null>(null);
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
loadSubscriptions();
|
||||
}, []);
|
||||
const params = new URLSearchParams(location.search);
|
||||
const accountId = params.get('account_id');
|
||||
loadSubscriptions(accountId ? Number(accountId) : undefined);
|
||||
}, [location.search]);
|
||||
|
||||
const loadSubscriptions = async () => {
|
||||
const loadSubscriptions = async (accountId?: number) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await fetchAPI('/v1/admin/subscriptions/');
|
||||
const query = accountId ? `?account_id=${accountId}` : '';
|
||||
const data = await fetchAPI(`/v1/admin/subscriptions/${query}`);
|
||||
setSubscriptions(data.results || []);
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to load subscriptions');
|
||||
@@ -45,6 +54,25 @@ export default function AdminSubscriptionsPage() {
|
||||
return statusFilter === 'all' || sub.status === statusFilter;
|
||||
});
|
||||
|
||||
const changeStatus = async (id: number, action: 'activate' | 'cancel') => {
|
||||
try {
|
||||
setActionLoadingId(id);
|
||||
const endpoint = action === 'activate'
|
||||
? `/v1/admin/subscriptions/${id}/activate/`
|
||||
: `/v1/admin/subscriptions/${id}/cancel/`;
|
||||
await fetchAPI(endpoint, { method: 'POST' });
|
||||
await loadSubscriptions();
|
||||
} catch (err: any) {
|
||||
setError(err.message || 'Failed to update subscription');
|
||||
} finally {
|
||||
setActionLoadingId(null);
|
||||
}
|
||||
};
|
||||
|
||||
const refreshPlans = async () => {
|
||||
await loadSubscriptions();
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
@@ -114,8 +142,38 @@ export default function AdminSubscriptionsPage() {
|
||||
<td className="px-6 py-4 text-sm text-gray-600">
|
||||
{new Date(sub.current_period_end).toLocaleDateString()}
|
||||
</td>
|
||||
<td className="px-6 py-4 text-right">
|
||||
<button className="text-blue-600 hover:text-blue-700 text-sm">Manage</button>
|
||||
<td className="px-6 py-4 text-right space-x-2">
|
||||
{sub.status !== 'active' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => changeStatus(sub.id, 'activate')}
|
||||
disabled={actionLoadingId === sub.id}
|
||||
startIcon={actionLoadingId === sub.id ? <Loader2 className="w-4 h-4 animate-spin" /> : undefined}
|
||||
>
|
||||
Activate
|
||||
</Button>
|
||||
)}
|
||||
{sub.status === 'active' && (
|
||||
<Button
|
||||
variant="outline"
|
||||
tone="neutral"
|
||||
size="sm"
|
||||
onClick={() => changeStatus(sub.id, 'cancel')}
|
||||
disabled={actionLoadingId === sub.id}
|
||||
startIcon={actionLoadingId === sub.id ? <Loader2 className="w-4 h-4 animate-spin" /> : undefined}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={refreshPlans}
|
||||
startIcon={<RefreshCw className="w-4 h-4" />}
|
||||
>
|
||||
Refresh
|
||||
</Button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
|
||||
Reference in New Issue
Block a user