feat: implement background payment sync worker and admin trigger UI
This commit is contained in:
60
components/admin/SyncPaymentsButton.tsx
Normal file
60
components/admin/SyncPaymentsButton.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
'use client';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { RefreshCw, Check, AlertCircle } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
export default function SyncPaymentsButton() {
|
||||
const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
|
||||
const [result, setResult] = useState<any>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const handleSync = async () => {
|
||||
if (status === 'loading') return;
|
||||
|
||||
setStatus('loading');
|
||||
try {
|
||||
const response = await fetch('/api/admin/sync-payments', {
|
||||
method: 'POST'
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
setResult(data);
|
||||
setStatus('success');
|
||||
router.refresh();
|
||||
setTimeout(() => setStatus('idle'), 3000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
setStatus('error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
{status === 'success' && result && (
|
||||
<div className="text-[10px] font-black text-emerald-600 bg-emerald-50 px-3 py-1.5 rounded-lg border border-emerald-100 flex items-center gap-2 animate-in fade-in slide-in-from-right-2">
|
||||
<Check size={12} />
|
||||
{result.processedCount} İŞLEM TARANDI
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleSync}
|
||||
disabled={status === 'loading'}
|
||||
className={`flex items-center justify-center gap-2 px-6 py-3 rounded-2xl text-sm font-bold transition shadow-lg ${
|
||||
status === 'loading' ? 'bg-gray-100 text-gray-400 cursor-not-allowed' :
|
||||
status === 'error' ? 'bg-red-50 text-red-600 border border-red-100' :
|
||||
'bg-emerald-600 text-white hover:bg-emerald-700 shadow-emerald-100'
|
||||
}`}
|
||||
>
|
||||
<RefreshCw size={18} className={status === 'loading' ? 'animate-spin' : ''} />
|
||||
{status === 'loading' ? 'Senkronize Ediliyor...' : 'Ödemeleri Eşitle'}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -108,6 +108,29 @@ export default function CryptoCheckout({ amount, currency, txId, wallets, onSucc
|
||||
setSelectedToken(network.tokens[0]);
|
||||
};
|
||||
|
||||
// Save payment intent to DB so background sync knows what to check
|
||||
useEffect(() => {
|
||||
if (!txId || !selectedNetwork || !selectedToken) return;
|
||||
|
||||
const saveIntent = async () => {
|
||||
try {
|
||||
await fetch(`/api/transactions/${txId}/intent`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
network: selectedNetwork.id,
|
||||
token: selectedToken.symbol
|
||||
})
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Failed to save payment intent:", err);
|
||||
}
|
||||
};
|
||||
|
||||
const timer = setTimeout(saveIntent, 1000); // Debounce
|
||||
return () => clearTimeout(timer);
|
||||
}, [txId, selectedNetwork.id, selectedToken.symbol]);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard.writeText(depositAddress);
|
||||
setCopied(true);
|
||||
|
||||
Reference in New Issue
Block a user