first commit

This commit is contained in:
Michaël
2026-01-29 14:13:11 -03:00
commit 2302748c87
105 changed files with 93301 additions and 0 deletions

View File

@@ -0,0 +1,461 @@
'use client';
import { useState, useEffect } from 'react';
import { useLanguage } from '@/context/LanguageContext';
import { paymentOptionsApi, PaymentOptionsConfig } from '@/lib/api';
import Card from '@/components/ui/Card';
import Button from '@/components/ui/Button';
import Input from '@/components/ui/Input';
import {
CreditCardIcon,
BanknotesIcon,
BoltIcon,
BuildingLibraryIcon,
CheckCircleIcon,
XCircleIcon,
ArrowPathIcon,
} from '@heroicons/react/24/outline';
import toast from 'react-hot-toast';
export default function PaymentOptionsPage() {
const { t, locale } = useLanguage();
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [options, setOptions] = useState<PaymentOptionsConfig>({
tpagoEnabled: false,
tpagoLink: null,
tpagoInstructions: null,
tpagoInstructionsEs: null,
bankTransferEnabled: false,
bankName: null,
bankAccountHolder: null,
bankAccountNumber: null,
bankAlias: null,
bankPhone: null,
bankNotes: null,
bankNotesEs: null,
lightningEnabled: true,
cashEnabled: true,
cashInstructions: null,
cashInstructionsEs: null,
});
useEffect(() => {
loadOptions();
}, []);
const loadOptions = async () => {
try {
setLoading(true);
const { paymentOptions } = await paymentOptionsApi.getGlobal();
setOptions(paymentOptions);
} catch (error) {
console.error('Failed to load payment options:', error);
toast.error('Failed to load payment options');
} finally {
setLoading(false);
}
};
const handleSave = async () => {
try {
setSaving(true);
await paymentOptionsApi.updateGlobal(options);
toast.success('Payment options saved successfully');
} catch (error: any) {
toast.error(error.message || 'Failed to save payment options');
} finally {
setSaving(false);
}
};
const updateOption = <K extends keyof PaymentOptionsConfig>(
key: K,
value: PaymentOptionsConfig[K]
) => {
setOptions((prev) => ({ ...prev, [key]: value }));
};
if (loading) {
return (
<div className="flex items-center justify-center min-h-[400px]">
<div className="animate-spin w-8 h-8 border-4 border-primary-yellow border-t-transparent rounded-full" />
</div>
);
}
return (
<div className="max-w-4xl">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-primary-dark">
{locale === 'es' ? 'Opciones de Pago' : 'Payment Options'}
</h1>
<p className="text-gray-600 mt-1">
{locale === 'es'
? 'Configura los métodos de pago disponibles para todos los eventos'
: 'Configure payment methods available for all events'}
</p>
</div>
<Button onClick={handleSave} isLoading={saving}>
{locale === 'es' ? 'Guardar Cambios' : 'Save Changes'}
</Button>
</div>
{/* TPago / International Card */}
<Card className="mb-6">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
<CreditCardIcon className="w-5 h-5 text-blue-600" />
</div>
<div>
<h3 className="font-semibold text-lg">
{locale === 'es' ? 'TPago / Tarjeta Internacional' : 'TPago / International Card'}
</h3>
<p className="text-sm text-gray-500">
{locale === 'es' ? 'Pago manual - requiere aprobación' : 'Manual payment - requires approval'}
</p>
</div>
</div>
<button
onClick={() => updateOption('tpagoEnabled', !options.tpagoEnabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
options.tpagoEnabled ? 'bg-primary-yellow' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
options.tpagoEnabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{options.tpagoEnabled && (
<div className="space-y-4 pt-4 border-t">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Enlace de Pago TPago' : 'TPago Payment Link'}
</label>
<Input
value={options.tpagoLink || ''}
onChange={(e) => updateOption('tpagoLink', e.target.value || null)}
placeholder="https://www.tpago.com.py/links?alias=..."
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Instructions (English)
</label>
<textarea
value={options.tpagoInstructions || ''}
onChange={(e) => updateOption('tpagoInstructions', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Instructions for users..."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Instrucciones (Español)
</label>
<textarea
value={options.tpagoInstructionsEs || ''}
onChange={(e) => updateOption('tpagoInstructionsEs', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Instrucciones para usuarios..."
/>
</div>
</div>
</div>
)}
</div>
</Card>
{/* Bank Transfer */}
<Card className="mb-6">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
<BuildingLibraryIcon className="w-5 h-5 text-green-600" />
</div>
<div>
<h3 className="font-semibold text-lg">
{locale === 'es' ? 'Transferencia Bancaria' : 'Bank Transfer'}
</h3>
<p className="text-sm text-gray-500">
{locale === 'es' ? 'Pago manual - requiere aprobación' : 'Manual payment - requires approval'}
</p>
</div>
</div>
<button
onClick={() => updateOption('bankTransferEnabled', !options.bankTransferEnabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
options.bankTransferEnabled ? 'bg-primary-yellow' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
options.bankTransferEnabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{options.bankTransferEnabled && (
<div className="space-y-4 pt-4 border-t">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Nombre del Banco' : 'Bank Name'}
</label>
<Input
value={options.bankName || ''}
onChange={(e) => updateOption('bankName', e.target.value || null)}
placeholder="e.g., Banco Itaú"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Titular de la Cuenta' : 'Account Holder'}
</label>
<Input
value={options.bankAccountHolder || ''}
onChange={(e) => updateOption('bankAccountHolder', e.target.value || null)}
placeholder="e.g., Juan Pérez"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Número de Cuenta' : 'Account Number'}
</label>
<Input
value={options.bankAccountNumber || ''}
onChange={(e) => updateOption('bankAccountNumber', e.target.value || null)}
placeholder="e.g., 1234567890"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Alias
</label>
<Input
value={options.bankAlias || ''}
onChange={(e) => updateOption('bankAlias', e.target.value || null)}
placeholder="e.g., spanglish.pagos"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
{locale === 'es' ? 'Teléfono' : 'Phone Number'}
</label>
<Input
value={options.bankPhone || ''}
onChange={(e) => updateOption('bankPhone', e.target.value || null)}
placeholder="e.g., +595 981 123456"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Additional Notes (English)
</label>
<textarea
value={options.bankNotes || ''}
onChange={(e) => updateOption('bankNotes', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Additional notes for users..."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Notas Adicionales (Español)
</label>
<textarea
value={options.bankNotesEs || ''}
onChange={(e) => updateOption('bankNotesEs', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Notas adicionales para usuarios..."
/>
</div>
</div>
</div>
)}
</div>
</Card>
{/* Bitcoin Lightning */}
<Card className="mb-6">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-orange-100 rounded-full flex items-center justify-center">
<BoltIcon className="w-5 h-5 text-orange-600" />
</div>
<div>
<h3 className="font-semibold text-lg">Bitcoin Lightning</h3>
<p className="text-sm text-gray-500">
{locale === 'es' ? 'Pago instantáneo - confirmación automática' : 'Instant payment - automatic confirmation'}
</p>
</div>
</div>
<button
onClick={() => updateOption('lightningEnabled', !options.lightningEnabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
options.lightningEnabled ? 'bg-primary-yellow' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
options.lightningEnabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{options.lightningEnabled && (
<div className="pt-4 border-t">
<div className="bg-orange-50 border border-orange-200 rounded-lg p-4">
<p className="text-sm text-orange-800">
{locale === 'es'
? 'Lightning está configurado a través de las variables de entorno de LNbits. Verifica que LNBITS_URL y LNBITS_API_KEY estén configurados correctamente.'
: 'Lightning is configured via LNbits environment variables. Make sure LNBITS_URL and LNBITS_API_KEY are properly set.'}
</p>
</div>
</div>
)}
</div>
</Card>
{/* Cash at Door */}
<Card className="mb-6">
<div className="p-6">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-yellow-100 rounded-full flex items-center justify-center">
<BanknotesIcon className="w-5 h-5 text-yellow-600" />
</div>
<div>
<h3 className="font-semibold text-lg">
{locale === 'es' ? 'Efectivo en el Evento' : 'Cash at the Door'}
</h3>
<p className="text-sm text-gray-500">
{locale === 'es' ? 'Pago manual - requiere aprobación' : 'Manual payment - requires approval'}
</p>
</div>
</div>
<button
onClick={() => updateOption('cashEnabled', !options.cashEnabled)}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors ${
options.cashEnabled ? 'bg-primary-yellow' : 'bg-gray-300'
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
options.cashEnabled ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{options.cashEnabled && (
<div className="space-y-4 pt-4 border-t">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Instructions (English)
</label>
<textarea
value={options.cashInstructions || ''}
onChange={(e) => updateOption('cashInstructions', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Instructions for cash payments..."
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">
Instrucciones (Español)
</label>
<textarea
value={options.cashInstructionsEs || ''}
onChange={(e) => updateOption('cashInstructionsEs', e.target.value || null)}
rows={3}
className="w-full px-4 py-2 rounded-btn border border-secondary-light-gray focus:outline-none focus:ring-2 focus:ring-primary-yellow"
placeholder="Instrucciones para pagos en efectivo..."
/>
</div>
</div>
</div>
)}
</div>
</Card>
{/* Summary */}
<Card>
<div className="p-6">
<h3 className="font-semibold text-lg mb-4">
{locale === 'es' ? 'Resumen de Métodos Activos' : 'Active Methods Summary'}
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="flex items-center gap-2">
{options.tpagoEnabled ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" />
) : (
<XCircleIcon className="w-5 h-5 text-gray-300" />
)}
<span className={options.tpagoEnabled ? 'text-gray-900' : 'text-gray-400'}>
TPago
</span>
</div>
<div className="flex items-center gap-2">
{options.bankTransferEnabled ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" />
) : (
<XCircleIcon className="w-5 h-5 text-gray-300" />
)}
<span className={options.bankTransferEnabled ? 'text-gray-900' : 'text-gray-400'}>
{locale === 'es' ? 'Transferencia' : 'Bank Transfer'}
</span>
</div>
<div className="flex items-center gap-2">
{options.lightningEnabled ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" />
) : (
<XCircleIcon className="w-5 h-5 text-gray-300" />
)}
<span className={options.lightningEnabled ? 'text-gray-900' : 'text-gray-400'}>
Lightning
</span>
</div>
<div className="flex items-center gap-2">
{options.cashEnabled ? (
<CheckCircleIcon className="w-5 h-5 text-green-500" />
) : (
<XCircleIcon className="w-5 h-5 text-gray-300" />
)}
<span className={options.cashEnabled ? 'text-gray-900' : 'text-gray-400'}>
{locale === 'es' ? 'Efectivo' : 'Cash'}
</span>
</div>
</div>
</div>
</Card>
{/* Save button (bottom) */}
<div className="mt-6 flex justify-end">
<Button onClick={handleSave} isLoading={saving} size="lg">
{locale === 'es' ? 'Guardar Cambios' : 'Save Changes'}
</Button>
</div>
</div>
);
}