Files
khalij-pay/src/pages/Settings.jsx

497 lines
24 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { Save, Key, Globe, DollarSign, Bell, Shield, Database, Ticket } from 'lucide-react';
import { generalAPI } from '../services/api';
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import { getErrorMessage, getSuccessMessage } from '../utils/errorHandler';
const Settings = () => {
const [settings, setSettings] = useState({
paymentApiKey: '',
webhookUrl: '',
currency: 'USD',
notifications: {
email: true,
sms: false,
webhook: true
},
security: {
twoFactor: false,
sessionTimeout: 30
},
data: {
retentionPeriod: 365,
autoBackup: true
}
});
const [voucherConfig, setVoucherConfig] = useState({
expireInDays: 0,
refundUponVoucherExpiration: true,
canPurchaseByAgentVouchers: true,
canTopUpWithUserVouchers: true
});
const [loading, setLoading] = useState(false);
const [voucherLoading, setVoucherLoading] = useState(false);
const [message, setMessage] = useState('');
const [activeTab, setActiveTab] = useState('payment');
useEffect(() => {
loadSettings();
loadVoucherConfiguration();
}, []);
const loadSettings = () => {
const savedSettings = localStorage.getItem('adminSettings');
if (savedSettings) {
setSettings(JSON.parse(savedSettings));
}
};
const loadVoucherConfiguration = async () => {
try {
setVoucherLoading(true);
const config = await generalAPI.getVoucherConfiguration();
if (config) {
setVoucherConfig({
expireInDays: config.expireInDays ?? 0,
refundUponVoucherExpiration: config.refundUponVoucherExpiration ?? true,
canPurchaseByAgentVouchers: config.canPurchaseByAgentVouchers ?? true,
canTopUpWithUserVouchers: config.canTopUpWithUserVouchers ?? true
});
}
} catch (err) {
console.error('Error loading voucher configuration:', err);
toast.error(getErrorMessage(err));
} finally {
setVoucherLoading(false);
}
};
const handleInputChange = (section, field, value) => {
setSettings(prev => ({
...prev,
[section]: {
...prev[section],
[field]: value
}
}));
};
const handleDirectChange = (field, value) => {
setSettings(prev => ({
...prev,
[field]: value
}));
};
const handleSave = async () => {
setLoading(true);
setMessage('');
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
localStorage.setItem('adminSettings', JSON.stringify(settings));
setMessage('Settings saved successfully!');
setTimeout(() => setMessage(''), 3000);
} catch (error) {
setMessage('Failed to save settings');
} finally {
setLoading(false);
}
};
const handleVoucherSave = async () => {
setVoucherLoading(true);
try {
const result = await generalAPI.updateVoucherConfiguration(voucherConfig);
toast.success(getSuccessMessage(result) || 'Voucher configuration saved successfully');
await loadVoucherConfiguration();
} catch (err) {
toast.error(getErrorMessage(err));
} finally {
setVoucherLoading(false);
}
};
const handleVoucherChange = (field, value) => {
setVoucherConfig(prev => ({
...prev,
[field]: value
}));
};
const tabs = [
{ id: 'payment', label: 'Payment', icon: DollarSign },
{ id: 'notifications', label: 'Notifications', icon: Bell },
{ id: 'security', label: 'Security', icon: Shield },
{ id: 'data', label: 'Data', icon: Database },
{ id: 'voucher', label: 'Voucher', icon: Ticket }
];
const currencies = [
{ value: 'USD', label: 'US Dollar (USD)' },
{ value: 'EUR', label: 'Euro (EUR)' },
{ value: 'GBP', label: 'British Pound (GBP)' },
{ value: 'CAD', label: 'Canadian Dollar (CAD)' },
{ value: 'AUD', label: 'Australian Dollar (AUD)' },
{ value: 'JPY', label: 'Japanese Yen (JPY)' }
];
return (
<div className="p-6">
<ToastContainer position="top-right" autoClose={3000} />
<div className="mb-8">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Settings</h1>
<p className="text-gray-600 dark:text-gray-400">Configure your payment system preferences</p>
</div>
{/* Message */}
{message && (
<div className={`mb-6 p-4 rounded-lg ${
message.includes('success')
? 'bg-green-50 dark:bg-green-900 text-green-700 dark:text-green-300 border border-green-200 dark:border-green-700'
: 'bg-red-50 dark:bg-red-900 text-red-700 dark:text-red-300 border border-red-200 dark:border-red-700'
}`}>
{message}
</div>
)}
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Sidebar */}
<div className="lg:col-span-1">
<nav className="space-y-1">
{tabs.map((tab) => {
const Icon = tab.icon;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`w-full flex items-center px-3 py-2 text-sm font-medium rounded-lg transition-colors duration-200 ${
activeTab === tab.id
? 'bg-primary-100 text-primary-700 dark:bg-primary-900 dark:text-primary-300'
: 'text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800'
}`}
>
<Icon className="mr-3 h-5 w-5" />
{tab.label}
</button>
);
})}
</nav>
</div>
{/* Content */}
<div className="lg:col-span-3">
<div className="card p-6">
{/* Payment Settings */}
{activeTab === 'payment' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-6">Payment Configuration</h3>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<Key className="inline h-4 w-4 mr-1" />
Payment API Key
</label>
<input
type="password"
value={settings.paymentApiKey}
onChange={(e) => handleDirectChange('paymentApiKey', e.target.value)}
className="input-field"
placeholder="Enter your payment API key"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Your secure API key for payment processing
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<Globe className="inline h-4 w-4 mr-1" />
Webhook URL
</label>
<input
type="url"
value={settings.webhookUrl}
onChange={(e) => handleDirectChange('webhookUrl', e.target.value)}
className="input-field"
placeholder="https://your-domain.com/webhook"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
URL to receive payment notifications
</p>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
<DollarSign className="inline h-4 w-4 mr-1" />
Default Currency
</label>
<select
value={settings.currency}
onChange={(e) => handleDirectChange('currency', e.target.value)}
className="input-field"
>
{currencies.map(currency => (
<option key={currency.value} value={currency.value}>
{currency.label}
</option>
))}
</select>
</div>
</div>
</div>
)}
{/* Notification Settings */}
{activeTab === 'notifications' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-6">Notification Preferences</h3>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Email Notifications</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Receive notifications via email</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.notifications.email}
onChange={(e) => handleInputChange('notifications', 'email', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">SMS Notifications</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Receive notifications via SMS</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.notifications.sms}
onChange={(e) => handleInputChange('notifications', 'sms', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Webhook Notifications</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Send notifications to webhook URL</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.notifications.webhook}
onChange={(e) => handleInputChange('notifications', 'webhook', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
</div>
</div>
)}
{/* Security Settings */}
{activeTab === 'security' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-6">Security Settings</h3>
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Two-Factor Authentication</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Enable 2FA for enhanced security</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.security.twoFactor}
onChange={(e) => handleInputChange('security', 'twoFactor', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Session Timeout (minutes)
</label>
<input
type="number"
min="5"
max="480"
value={settings.security.sessionTimeout}
onChange={(e) => handleInputChange('security', 'sessionTimeout', parseInt(e.target.value))}
className="input-field w-32"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Automatically log out after inactivity
</p>
</div>
</div>
</div>
)}
{/* Data Settings */}
{activeTab === 'data' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-6">Data Management</h3>
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Data Retention Period (days)
</label>
<input
type="number"
min="30"
max="2555"
value={settings.data.retentionPeriod}
onChange={(e) => handleInputChange('data', 'retentionPeriod', parseInt(e.target.value))}
className="input-field w-32"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
How long to keep transaction data
</p>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Automatic Backup</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Automatically backup data daily</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={settings.data.autoBackup}
onChange={(e) => handleInputChange('data', 'autoBackup', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
</div>
</div>
)}
{/* Voucher Settings */}
{activeTab === 'voucher' && (
<div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-6">Voucher Configuration</h3>
{voucherLoading ? (
<div className="flex justify-center py-10">
<div className="w-8 h-8 border-4 border-t-transparent border-primary-500 rounded-full animate-spin" />
</div>
) : (
<div className="space-y-6">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Expire In Days
</label>
<input
type="number"
min="0"
value={voucherConfig.expireInDays}
onChange={(e) => handleVoucherChange('expireInDays', Number(e.target.value) || 0)}
className="input-field w-32"
/>
<p className="mt-1 text-xs text-gray-500 dark:text-gray-400">
Number of days before vouchers expire
</p>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Refund Upon Voucher Expiration</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Automatically refund when voucher expires</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={voucherConfig.refundUponVoucherExpiration}
onChange={(e) => handleVoucherChange('refundUponVoucherExpiration', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Can Purchase By Agent Vouchers</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Allow purchases using agent-generated vouchers</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={voucherConfig.canPurchaseByAgentVouchers}
onChange={(e) => handleVoucherChange('canPurchaseByAgentVouchers', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
<div className="flex items-center justify-between">
<div>
<label className="text-sm font-medium text-gray-700 dark:text-gray-300">Can Top Up With User Vouchers</label>
<p className="text-xs text-gray-500 dark:text-gray-400">Allow top-up using user-generated vouchers</p>
</div>
<label className="relative inline-flex items-center cursor-pointer">
<input
type="checkbox"
checked={voucherConfig.canTopUpWithUserVouchers}
onChange={(e) => handleVoucherChange('canTopUpWithUserVouchers', e.target.checked)}
className="sr-only peer"
/>
<div className="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-primary-300 dark:peer-focus:ring-primary-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-primary-600"></div>
</label>
</div>
</div>
)}
</div>
)}
{/* Save Button */}
<div className="mt-8 pt-6 border-t border-gray-200 dark:border-gray-700">
{activeTab === 'voucher' ? (
<button
onClick={handleVoucherSave}
disabled={voucherLoading}
className="btn-primary flex items-center"
>
<Save className="h-4 w-4 mr-2" />
{voucherLoading ? 'Saving...' : 'Save Voucher Settings'}
</button>
) : (
<button
onClick={handleSave}
disabled={loading}
className="btn-primary flex items-center"
>
<Save className="h-4 w-4 mr-2" />
{loading ? 'Saving...' : 'Save Settings'}
</button>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default Settings;