main: second iter

Signed-off-by: nfel <nfilsaraee@gmail.com>
This commit is contained in:
2025-12-27 22:41:36 +03:30
parent 8a7842e263
commit 9a7e627329
33 changed files with 4865 additions and 81 deletions
+97 -51
View File
@@ -14,8 +14,9 @@ interface PaymentModalProps {
export default function PaymentModal({ album, onClose, onSuccess }: PaymentModalProps) {
const [cardNumber, setCardNumber] = useState('');
const [cardName, setCardName] = useState('');
const [expiry, setExpiry] = useState('');
const [cvv, setCvv] = useState('');
const [phoneNumber, setPhoneNumber] = useState('');
const [email, setEmail] = useState('');
const [txReceipt, setTxReceipt] = useState('');
const [processing, setProcessing] = useState(false);
const [error, setError] = useState('');
@@ -25,12 +26,11 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
return groups ? groups.join(' ') : numbers;
};
const formatExpiry = (value: string) => {
const formatPhoneNumber = (value: string) => {
const numbers = value.replace(/\D/g, '');
if (numbers.length >= 2) {
return numbers.slice(0, 2) + '/' + numbers.slice(2, 4);
}
return numbers;
if (numbers.length <= 3) return numbers;
if (numbers.length <= 6) return `(${numbers.slice(0, 3)}) ${numbers.slice(3)}`;
return `(${numbers.slice(0, 3)}) ${numbers.slice(3, 6)}-${numbers.slice(6, 10)}`;
};
const handleSubmit = async (e: React.FormEvent) => {
@@ -44,32 +44,60 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
}
if (!cardName.trim()) {
setError('Card name is required');
setError('Name is required');
return;
}
if (expiry.length !== 5) {
setError('Invalid expiry date');
if (phoneNumber.replace(/\D/g, '').length < 10) {
setError('Invalid phone number');
return;
}
if (cvv.length !== 3 && cvv.length !== 4) {
setError('Invalid CVV');
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
setError('Invalid email address');
return;
}
if (!txReceipt.trim()) {
setError('Transaction receipt is required');
return;
}
if (!album) return;
setProcessing(true);
// Simulate payment processing
setTimeout(() => {
// Generate a mock transaction ID
const transactionId = 'TXN-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9).toUpperCase();
try {
// Use the provided transaction receipt as the ID
const transactionId = txReceipt.trim() || 'TXN-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9).toUpperCase();
// Create purchase via API
const response = await fetch('/api/purchases', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
albumId: album.id,
transactionId,
customerName: cardName.trim(),
email: email.trim(),
phoneNumber: phoneNumber,
txReceipt: txReceipt.trim(),
purchaseDate: new Date().getTime(),
}),
});
if (!response.ok) {
const data = await response.json();
throw new Error(data.error || 'Failed to process purchase');
}
setProcessing(false);
if (album) {
onSuccess(album.id, transactionId);
}
}, 2000);
onSuccess(album.id, transactionId);
} catch (err: any) {
setProcessing(false);
setError(err.message || 'Failed to process payment');
}
};
if (!album) return null;
@@ -136,46 +164,64 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
{/* Card Name */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Name on Card
Full Name
</label>
<input
type="text"
value={cardName}
onChange={(e) => setCardName(e.target.value.toUpperCase())}
placeholder="JOHN DOE"
onChange={(e) => setCardName(e.target.value)}
placeholder="John Doe"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
{/* Expiry and CVV */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Expiry Date
</label>
<input
type="text"
value={expiry}
onChange={(e) => setExpiry(formatExpiry(e.target.value))}
placeholder="MM/YY"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
CVV
</label>
<input
type="text"
value={cvv}
onChange={(e) => setCvv(e.target.value.replace(/\D/g, '').slice(0, 4))}
placeholder="123"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
{/* Phone Number */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Phone Number
</label>
<input
type="tel"
value={phoneNumber}
onChange={(e) => {
const formatted = formatPhoneNumber(e.target.value);
setPhoneNumber(formatted);
}}
placeholder="(123) 456-7890"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
{/* Email */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Email Address
</label>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="john@example.com"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
{/* Transaction Receipt */}
<div>
<label className="block text-sm font-medium text-gray-300 mb-2">
Transaction Receipt / ID
</label>
<input
type="text"
value={txReceipt}
onChange={(e) => setTxReceipt(e.target.value)}
placeholder="TXN-123456789"
className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-lg focus:border-accent-cyan focus:outline-none focus:ring-2 focus:ring-accent-cyan/50 text-white placeholder-gray-500"
required
/>
</div>
{/* Error Message */}