feat: Add customer management UI (edit, delete, suspend, activate)
This commit is contained in:
parent
0092003a7f
commit
79d6c3c5e6
|
|
@ -68,6 +68,85 @@ def get_customer(current_admin, customer_id):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
@customers_bp.route('/<int:customer_id>', methods=['PUT'])
|
||||||
|
@token_required
|
||||||
|
def update_customer(current_admin, customer_id):
|
||||||
|
"""Update customer information"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
result = call_customer_api(
|
||||||
|
f'/api/admin/customers/{customer_id}',
|
||||||
|
method='PUT',
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get('status') == 'success':
|
||||||
|
return jsonify(result), 200
|
||||||
|
else:
|
||||||
|
return jsonify(result or {'error': 'Failed to update customer'}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@customers_bp.route('/<int:customer_id>', methods=['DELETE'])
|
||||||
|
@token_required
|
||||||
|
def delete_customer(current_admin, customer_id):
|
||||||
|
"""Delete customer"""
|
||||||
|
try:
|
||||||
|
result = call_customer_api(
|
||||||
|
f'/api/admin/customers/{customer_id}',
|
||||||
|
method='DELETE'
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get('status') == 'success':
|
||||||
|
return jsonify(result), 200
|
||||||
|
else:
|
||||||
|
return jsonify(result or {'error': 'Failed to delete customer'}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@customers_bp.route('/<int:customer_id>/suspend', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def suspend_customer(current_admin, customer_id):
|
||||||
|
"""Suspend customer"""
|
||||||
|
try:
|
||||||
|
result = call_customer_api(
|
||||||
|
f'/api/admin/customers/{customer_id}/suspend',
|
||||||
|
method='POST'
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get('status') == 'success':
|
||||||
|
return jsonify(result), 200
|
||||||
|
else:
|
||||||
|
return jsonify(result or {'error': 'Failed to suspend customer'}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@customers_bp.route('/<int:customer_id>/activate', methods=['POST'])
|
||||||
|
@token_required
|
||||||
|
def activate_customer(current_admin, customer_id):
|
||||||
|
"""Activate customer"""
|
||||||
|
try:
|
||||||
|
result = call_customer_api(
|
||||||
|
f'/api/admin/customers/{customer_id}/activate',
|
||||||
|
method='POST'
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get('status') == 'success':
|
||||||
|
return jsonify(result), 200
|
||||||
|
else:
|
||||||
|
return jsonify(result or {'error': 'Failed to activate customer'}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@customers_bp.route('/<int:customer_id>/plan', methods=['PUT'])
|
@customers_bp.route('/<int:customer_id>/plan', methods=['PUT'])
|
||||||
@token_required
|
@token_required
|
||||||
def update_customer_plan(current_admin, customer_id):
|
def update_customer_plan(current_admin, customer_id):
|
||||||
|
|
@ -81,14 +160,37 @@ def update_customer_plan(current_admin, customer_id):
|
||||||
data=data
|
data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
if result:
|
if result and result.get('status') == 'success':
|
||||||
return jsonify(result), 200
|
return jsonify(result), 200
|
||||||
else:
|
else:
|
||||||
return jsonify({'error': 'Failed to update plan'}), 500
|
return jsonify(result or {'error': 'Failed to update plan'}), 500
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)}), 500
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@customers_bp.route('/<int:customer_id>/status', methods=['PUT'])
|
||||||
|
@token_required
|
||||||
|
def update_customer_status(current_admin, customer_id):
|
||||||
|
"""Update customer status"""
|
||||||
|
try:
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
result = call_customer_api(
|
||||||
|
f'/api/admin/customers/{customer_id}/status',
|
||||||
|
method='PUT',
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
|
||||||
|
if result and result.get('status') == 'success':
|
||||||
|
return jsonify(result), 200
|
||||||
|
else:
|
||||||
|
return jsonify(result or {'error': 'Failed to update status'}), 500
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({'error': str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
@customers_bp.route('/stats', methods=['GET'])
|
@customers_bp.route('/stats', methods=['GET'])
|
||||||
@token_required
|
@token_required
|
||||||
def get_customer_stats(current_admin):
|
def get_customer_stats(current_admin):
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import Layout from '../components/Layout';
|
import Layout from '../components/Layout';
|
||||||
import api from '../services/api';
|
import api from '../services/api';
|
||||||
|
import customerService from '../services/customerService';
|
||||||
|
|
||||||
const Customers = () => {
|
const Customers = () => {
|
||||||
const [customers, setCustomers] = useState([]);
|
const [customers, setCustomers] = useState([]);
|
||||||
|
|
@ -11,6 +12,8 @@ const Customers = () => {
|
||||||
const [stats, setStats] = useState(null);
|
const [stats, setStats] = useState(null);
|
||||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||||
const [showPlanModal, setShowPlanModal] = useState(false);
|
const [showPlanModal, setShowPlanModal] = useState(false);
|
||||||
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchCustomers();
|
fetchCustomers();
|
||||||
|
|
@ -42,13 +45,66 @@ const Customers = () => {
|
||||||
|
|
||||||
const handleUpdatePlan = async (customerId, planData) => {
|
const handleUpdatePlan = async (customerId, planData) => {
|
||||||
try {
|
try {
|
||||||
await api.put(`/api/customers/${customerId}/plan`, planData);
|
await customerService.updateCustomerPlan(customerId, planData);
|
||||||
fetchCustomers();
|
fetchCustomers();
|
||||||
setShowPlanModal(false);
|
setShowPlanModal(false);
|
||||||
setSelectedCustomer(null);
|
setSelectedCustomer(null);
|
||||||
|
alert('Plan updated successfully!');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to update plan:', err);
|
console.error('Failed to update plan:', err);
|
||||||
alert('Failed to update plan');
|
alert('Failed to update plan: ' + (err.response?.data?.message || err.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateCustomer = async (customerId, customerData) => {
|
||||||
|
try {
|
||||||
|
await customerService.updateCustomer(customerId, customerData);
|
||||||
|
fetchCustomers();
|
||||||
|
setShowEditModal(false);
|
||||||
|
setSelectedCustomer(null);
|
||||||
|
alert('Customer updated successfully!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to update customer:', err);
|
||||||
|
alert('Failed to update customer: ' + (err.response?.data?.message || err.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteCustomer = async (customerId) => {
|
||||||
|
try {
|
||||||
|
await customerService.deleteCustomer(customerId);
|
||||||
|
fetchCustomers();
|
||||||
|
setShowDeleteConfirm(false);
|
||||||
|
setSelectedCustomer(null);
|
||||||
|
alert('Customer deleted successfully!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to delete customer:', err);
|
||||||
|
alert('Failed to delete customer: ' + (err.response?.data?.message || err.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSuspendCustomer = async (customerId) => {
|
||||||
|
if (!confirm('Are you sure you want to suspend this customer?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await customerService.suspendCustomer(customerId);
|
||||||
|
fetchCustomers();
|
||||||
|
alert('Customer suspended successfully!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to suspend customer:', err);
|
||||||
|
alert('Failed to suspend customer: ' + (err.response?.data?.message || err.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleActivateCustomer = async (customerId) => {
|
||||||
|
if (!confirm('Are you sure you want to activate this customer?')) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await customerService.activateCustomer(customerId);
|
||||||
|
fetchCustomers();
|
||||||
|
alert('Customer activated successfully!');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to activate customer:', err);
|
||||||
|
alert('Failed to activate customer: ' + (err.response?.data?.message || err.message));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -166,15 +222,55 @@ const Customers = () => {
|
||||||
<td>{getStatusBadge(customer.is_active, customer.subscription_status)}</td>
|
<td>{getStatusBadge(customer.is_active, customer.subscription_status)}</td>
|
||||||
<td>{new Date(customer.created_at).toLocaleDateString()}</td>
|
<td>{new Date(customer.created_at).toLocaleDateString()}</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<div className="flex gap-2">
|
||||||
onClick={() => {
|
<button
|
||||||
setSelectedCustomer(customer);
|
onClick={() => {
|
||||||
setShowPlanModal(true);
|
setSelectedCustomer(customer);
|
||||||
}}
|
setShowEditModal(true);
|
||||||
className="text-primary hover:text-primary-dark mr-3"
|
}}
|
||||||
>
|
className="text-blue-600 hover:text-blue-800"
|
||||||
Edit Plan
|
title="Edit Customer"
|
||||||
</button>
|
>
|
||||||
|
✏️
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedCustomer(customer);
|
||||||
|
setShowPlanModal(true);
|
||||||
|
}}
|
||||||
|
className="text-purple-600 hover:text-purple-800"
|
||||||
|
title="Edit Plan"
|
||||||
|
>
|
||||||
|
📦
|
||||||
|
</button>
|
||||||
|
{customer.subscription_status === 'suspended' || !customer.is_active ? (
|
||||||
|
<button
|
||||||
|
onClick={() => handleActivateCustomer(customer.id)}
|
||||||
|
className="text-green-600 hover:text-green-800"
|
||||||
|
title="Activate Customer"
|
||||||
|
>
|
||||||
|
✅
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<button
|
||||||
|
onClick={() => handleSuspendCustomer(customer.id)}
|
||||||
|
className="text-orange-600 hover:text-orange-800"
|
||||||
|
title="Suspend Customer"
|
||||||
|
>
|
||||||
|
⏸️
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedCustomer(customer);
|
||||||
|
setShowDeleteConfirm(true);
|
||||||
|
}}
|
||||||
|
className="text-red-600 hover:text-red-800"
|
||||||
|
title="Delete Customer"
|
||||||
|
>
|
||||||
|
🗑️
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
|
|
@ -184,6 +280,18 @@ const Customers = () => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Edit Customer Modal */}
|
||||||
|
{showEditModal && selectedCustomer && (
|
||||||
|
<EditCustomerModal
|
||||||
|
customer={selectedCustomer}
|
||||||
|
onClose={() => {
|
||||||
|
setShowEditModal(false);
|
||||||
|
setSelectedCustomer(null);
|
||||||
|
}}
|
||||||
|
onUpdate={handleUpdateCustomer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Plan Update Modal */}
|
{/* Plan Update Modal */}
|
||||||
{showPlanModal && selectedCustomer && (
|
{showPlanModal && selectedCustomer && (
|
||||||
<PlanModal
|
<PlanModal
|
||||||
|
|
@ -195,10 +303,160 @@ const Customers = () => {
|
||||||
onUpdate={handleUpdatePlan}
|
onUpdate={handleUpdatePlan}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Delete Confirmation Modal */}
|
||||||
|
{showDeleteConfirm && selectedCustomer && (
|
||||||
|
<DeleteConfirmModal
|
||||||
|
customer={selectedCustomer}
|
||||||
|
onClose={() => {
|
||||||
|
setShowDeleteConfirm(false);
|
||||||
|
setSelectedCustomer(null);
|
||||||
|
}}
|
||||||
|
onConfirm={handleDeleteCustomer}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Edit Customer Modal Component
|
||||||
|
const EditCustomerModal = ({ customer, onClose, onUpdate }) => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
full_name: customer.full_name || '',
|
||||||
|
email: customer.email || '',
|
||||||
|
company_name: customer.company_name || '',
|
||||||
|
phone: customer.phone || '',
|
||||||
|
billing_address: customer.billing_address || '',
|
||||||
|
billing_city: customer.billing_city || '',
|
||||||
|
billing_country: customer.billing_country || '',
|
||||||
|
billing_postal_code: customer.billing_postal_code || ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
onUpdate(customer.id, formData);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
|
||||||
|
<h2 className="text-2xl font-bold mb-4">Edit Customer - {customer.full_name}</h2>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Full Name *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.full_name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, full_name: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Email *
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Company Name
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.company_name}
|
||||||
|
onChange={(e) => setFormData({ ...formData, company_name: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Phone
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={(e) => setFormData({ ...formData, phone: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4 col-span-2">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Billing Address
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
className="input w-full"
|
||||||
|
rows="2"
|
||||||
|
value={formData.billing_address}
|
||||||
|
onChange={(e) => setFormData({ ...formData, billing_address: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
City
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.billing_city}
|
||||||
|
onChange={(e) => setFormData({ ...formData, billing_city: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Country
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.billing_country}
|
||||||
|
onChange={(e) => setFormData({ ...formData, billing_country: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-4">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Postal Code
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
className="input w-full"
|
||||||
|
value={formData.billing_postal_code}
|
||||||
|
onChange={(e) => setFormData({ ...formData, billing_postal_code: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button type="submit" className="btn-primary flex-1">
|
||||||
|
Update Customer
|
||||||
|
</button>
|
||||||
|
<button type="button" onClick={onClose} className="btn-outline flex-1">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
// Plan Update Modal Component
|
// Plan Update Modal Component
|
||||||
const PlanModal = ({ customer, onClose, onUpdate }) => {
|
const PlanModal = ({ customer, onClose, onUpdate }) => {
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
|
|
@ -290,5 +548,53 @@ const PlanModal = ({ customer, onClose, onUpdate }) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Delete Confirmation Modal Component
|
||||||
|
const DeleteConfirmModal = ({ customer, onClose, onConfirm }) => {
|
||||||
|
const handleConfirm = () => {
|
||||||
|
onConfirm(customer.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
||||||
|
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4">
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<div className="text-6xl mb-4">⚠️</div>
|
||||||
|
<h2 className="text-2xl font-bold text-red-600 mb-2">Delete Customer</h2>
|
||||||
|
<p className="text-gray-700">
|
||||||
|
Are you sure you want to delete <strong>{customer.full_name}</strong>?
|
||||||
|
</p>
|
||||||
|
<p className="text-sm text-gray-600 mt-2">
|
||||||
|
This will permanently delete:
|
||||||
|
</p>
|
||||||
|
<ul className="text-sm text-gray-600 mt-2 text-left list-disc list-inside">
|
||||||
|
<li>Customer account and profile</li>
|
||||||
|
<li>All domains ({customer.domain_count || 0})</li>
|
||||||
|
<li>All DNS records</li>
|
||||||
|
<li>All associated data</li>
|
||||||
|
</ul>
|
||||||
|
<p className="text-sm text-red-600 font-semibold mt-3">
|
||||||
|
This action cannot be undone!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={handleConfirm}
|
||||||
|
className="flex-1 bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700"
|
||||||
|
>
|
||||||
|
Yes, Delete
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="flex-1 btn-outline"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Customers;
|
export default Customers;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
import api from './api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customer Management Service
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get all customers
|
||||||
|
export const getCustomers = async (params = {}) => {
|
||||||
|
const response = await api.get('/api/customers', { params });
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get single customer
|
||||||
|
export const getCustomer = async (customerId) => {
|
||||||
|
const response = await api.get(`/api/customers/${customerId}`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update customer information
|
||||||
|
export const updateCustomer = async (customerId, data) => {
|
||||||
|
const response = await api.put(`/api/customers/${customerId}`, data);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete customer
|
||||||
|
export const deleteCustomer = async (customerId) => {
|
||||||
|
const response = await api.delete(`/api/customers/${customerId}`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Suspend customer
|
||||||
|
export const suspendCustomer = async (customerId) => {
|
||||||
|
const response = await api.post(`/api/customers/${customerId}/suspend`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Activate customer
|
||||||
|
export const activateCustomer = async (customerId) => {
|
||||||
|
const response = await api.post(`/api/customers/${customerId}/activate`);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update customer plan
|
||||||
|
export const updateCustomerPlan = async (customerId, planData) => {
|
||||||
|
const response = await api.put(`/api/customers/${customerId}/plan`, planData);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update customer status
|
||||||
|
export const updateCustomerStatus = async (customerId, statusData) => {
|
||||||
|
const response = await api.put(`/api/customers/${customerId}/status`, statusData);
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get customer statistics
|
||||||
|
export const getCustomerStats = async () => {
|
||||||
|
const response = await api.get('/api/customers/stats');
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getCustomers,
|
||||||
|
getCustomer,
|
||||||
|
updateCustomer,
|
||||||
|
deleteCustomer,
|
||||||
|
suspendCustomer,
|
||||||
|
activateCustomer,
|
||||||
|
updateCustomerPlan,
|
||||||
|
updateCustomerStatus,
|
||||||
|
getCustomerStats,
|
||||||
|
};
|
||||||
|
|
||||||
Loading…
Reference in New Issue