main: add many things to app :)
Signed-off-by: nfel <nfilsaraee@gmail.com>
This commit is contained in:
@@ -29,7 +29,7 @@ export default function AdminSidebar() {
|
||||
<h1 className="text-2xl font-bold text-paper-dark border-b-4 border-paper-dark inline-block pb-1">
|
||||
Admin Panel
|
||||
</h1>
|
||||
<p className="text-sm text-paper-gray mt-3">Parsa Music Store</p>
|
||||
<p className="text-sm text-paper-gray mt-3">Podzahr</p>
|
||||
</div>
|
||||
|
||||
{/* Menu */}
|
||||
|
||||
@@ -43,7 +43,8 @@ export default function Biography() {
|
||||
<h1 className="text-4xl md:text-5xl font-bold text-paper-dark mb-2 tracking-tight border-b-4 border-paper-dark inline-block pb-1">
|
||||
{artistBio.name}
|
||||
</h1>
|
||||
<p className="text-xl text-paper-brown mb-6 font-medium mt-4">{artistBio.title}</p>
|
||||
<p className="text-lg text-paper-gray mb-2 mt-3">{artistBio.nickname}</p>
|
||||
<p className="text-xl text-paper-brown mb-6 font-medium">{artistBio.title}</p>
|
||||
</motion.div>
|
||||
|
||||
<motion.div
|
||||
|
||||
@@ -5,6 +5,7 @@ import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { FaTimes, FaTrash, FaShoppingCart } from 'react-icons/fa';
|
||||
import { useCart } from '@/lib/CartContext';
|
||||
import { Album, Purchase } from '@/lib/types';
|
||||
import { formatPrice } from '@/lib/utils';
|
||||
import PaymentModal from './PaymentModal';
|
||||
import PurchaseSuccessModal from './PurchaseSuccessModal';
|
||||
|
||||
@@ -37,6 +38,7 @@ export default function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
|
||||
albumId,
|
||||
transactionId,
|
||||
purchaseDate: new Date(),
|
||||
approvalStatus: 'pending',
|
||||
};
|
||||
|
||||
// Load existing purchases
|
||||
@@ -45,7 +47,7 @@ export default function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
|
||||
const updatedPurchases = [...existingPurchases, purchase];
|
||||
|
||||
localStorage.setItem('purchases', JSON.stringify(updatedPurchases));
|
||||
setPurchasedAlbums([...purchasedAlbums, albumId]);
|
||||
// Don't add to purchasedAlbums yet - wait for approval
|
||||
setLatestPurchase(purchase);
|
||||
|
||||
// Remove from cart
|
||||
@@ -134,7 +136,7 @@ export default function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
|
||||
{item.album.year} • {item.album.songs.length} tracks
|
||||
</p>
|
||||
<p className="text-paper-dark font-bold mt-2">
|
||||
${item.album.price}
|
||||
{formatPrice(item.album.price)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -160,7 +162,7 @@ export default function CartSidebar({ isOpen, onClose }: CartSidebarProps) {
|
||||
<div className="flex items-center justify-between text-xl p-4 paper-card-dark">
|
||||
<span className="text-paper-light font-semibold">Total</span>
|
||||
<span className="text-paper-sand font-bold">
|
||||
${getCartTotal().toFixed(2)}
|
||||
{formatPrice(getCartTotal())}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -38,9 +38,9 @@ export default function Header({ onCartClick }: HeaderProps) {
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-paper-dark tracking-tight">
|
||||
Parsa
|
||||
Podzahr
|
||||
</h1>
|
||||
<p className="text-xs text-paper-brown">Progressive Rock</p>
|
||||
<p className="text-xs text-paper-brown">Parsa Sadatie</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
|
||||
+27
-27
@@ -125,18 +125,18 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
animate={{ opacity: 1, scale: 1, y: 0 }}
|
||||
exit={{ opacity: 0, scale: 0.9, y: 50 }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-6 md:p-12 pointer-events-none"
|
||||
className="fixed inset-0 z-50 flex items-center justify-center p-4 pointer-events-none overflow-y-auto"
|
||||
>
|
||||
<div className="w-full max-w-sm pointer-events-auto relative">
|
||||
<div className="w-full max-w-sm pointer-events-auto relative my-auto">
|
||||
{/* Close Button - Top Right */}
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={onClose}
|
||||
className="absolute -top-2 -right-2 z-10 p-3 bg-paper-light hover:bg-paper-sand border-2 border-paper-dark transition-colors shadow-paper-lg"
|
||||
className="absolute -top-2 -right-2 z-10 p-2 bg-paper-light hover:bg-paper-sand border-2 border-paper-dark transition-colors shadow-paper-lg"
|
||||
aria-label="Close player"
|
||||
>
|
||||
<FaTimes className="text-xl text-paper-dark" />
|
||||
<FaTimes className="text-lg text-paper-dark" />
|
||||
</motion.button>
|
||||
|
||||
{/* Album Artwork */}
|
||||
@@ -144,7 +144,7 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className="relative aspect-square overflow-hidden mb-6 shadow-paper-lg border-4 border-paper-dark"
|
||||
className="relative aspect-square overflow-hidden mb-3 shadow-paper-lg border-4 border-paper-dark"
|
||||
>
|
||||
<div className="absolute inset-0 bg-paper-brown"></div>
|
||||
<div className="absolute inset-0 cardboard-texture"></div>
|
||||
@@ -156,8 +156,8 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
|
||||
{/* Preview Badge */}
|
||||
{!isPurchased && (
|
||||
<div className="absolute top-3 right-3 bg-paper-dark text-paper-light px-3 py-1 border-2 border-paper-brown text-xs font-semibold shadow-paper">
|
||||
Preview Mode
|
||||
<div className="absolute top-2 right-2 bg-paper-dark text-paper-light px-2 py-1 border-2 border-paper-brown text-xs font-semibold shadow-paper">
|
||||
Preview
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
@@ -167,15 +167,15 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className="text-center mb-6 p-4 paper-card"
|
||||
className="text-center mb-3 p-3 paper-card"
|
||||
>
|
||||
<h2 className="text-xl md:text-2xl font-bold text-paper-dark mb-1">
|
||||
<h2 className="text-lg font-bold text-paper-dark mb-0.5">
|
||||
{currentSong?.title}
|
||||
</h2>
|
||||
<p className="text-base text-paper-brown">
|
||||
<p className="text-sm text-paper-brown">
|
||||
{album.title}
|
||||
</p>
|
||||
<p className="text-xs text-paper-gray mt-1">
|
||||
<p className="text-xs text-paper-gray mt-0.5">
|
||||
Track {currentSongIndex + 1} of {album.songs.length}
|
||||
</p>
|
||||
</motion.div>
|
||||
@@ -185,7 +185,7 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
className="mb-5 p-4 paper-card-light"
|
||||
className="mb-3 p-3 paper-card-light"
|
||||
>
|
||||
<div className="relative">
|
||||
<input
|
||||
@@ -212,29 +212,29 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.4 }}
|
||||
className="flex items-center justify-center gap-6 mb-6 p-4 paper-card"
|
||||
className="flex items-center justify-center gap-4 mb-3 p-3 paper-card"
|
||||
>
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={handlePrevious}
|
||||
className="p-3 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors"
|
||||
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors"
|
||||
aria-label="Previous track"
|
||||
>
|
||||
<FaStepBackward className="text-xl text-paper-dark" />
|
||||
<FaStepBackward className="text-lg text-paper-dark" />
|
||||
</motion.button>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={togglePlay}
|
||||
className="p-5 bg-paper-dark hover:bg-paper-brown border-2 border-paper-brown transition-all shadow-paper-lg"
|
||||
className="p-4 bg-paper-dark hover:bg-paper-brown border-2 border-paper-brown transition-all shadow-paper-lg"
|
||||
aria-label={isPlaying ? 'Pause' : 'Play'}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<FaPause className="text-2xl text-paper-light" />
|
||||
<FaPause className="text-xl text-paper-light" />
|
||||
) : (
|
||||
<FaPlay className="text-2xl text-paper-light ml-0.5" />
|
||||
<FaPlay className="text-xl text-paper-light ml-0.5" />
|
||||
)}
|
||||
</motion.button>
|
||||
|
||||
@@ -242,10 +242,10 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
whileHover={{ scale: 1.05 }}
|
||||
whileTap={{ scale: 0.95 }}
|
||||
onClick={handleNext}
|
||||
className="p-3 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors"
|
||||
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-transparent hover:border-paper-dark transition-colors"
|
||||
aria-label="Next track"
|
||||
>
|
||||
<FaStepForward className="text-xl text-paper-dark" />
|
||||
<FaStepForward className="text-lg text-paper-dark" />
|
||||
</motion.button>
|
||||
</motion.div>
|
||||
|
||||
@@ -258,7 +258,7 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={() => onPurchase(album)}
|
||||
className="w-full mb-3 py-3 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light text-base transition-all shadow-paper-lg"
|
||||
className="w-full mb-2 py-2.5 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light text-sm transition-all shadow-paper-lg"
|
||||
>
|
||||
Purchase Full Album - {formatPrice(album.price)}
|
||||
</motion.button>
|
||||
@@ -272,7 +272,7 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
>
|
||||
<button
|
||||
onClick={() => setShowTrackList(!showTrackList)}
|
||||
className="w-full py-2.5 bg-paper-sand hover:bg-paper-gray border-2 border-paper-brown transition-colors flex items-center justify-center gap-2 text-paper-dark text-sm font-medium"
|
||||
className="w-full py-2 bg-paper-sand hover:bg-paper-gray border-2 border-paper-brown transition-colors flex items-center justify-center gap-2 text-paper-dark text-xs font-medium"
|
||||
>
|
||||
{showTrackList ? <FaChevronUp className="text-xs" /> : <FaChevronDown className="text-xs" />}
|
||||
<span className="font-medium">
|
||||
@@ -289,7 +289,7 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
transition={{ duration: 0.3 }}
|
||||
className="overflow-hidden"
|
||||
>
|
||||
<div className="mt-3 max-h-52 overflow-y-auto bg-paper-light border-2 border-paper-brown p-2">
|
||||
<div className="mt-2 max-h-40 overflow-y-auto bg-paper-light border-2 border-paper-brown p-1.5">
|
||||
<div className="space-y-0.5">
|
||||
{album.songs.map((song, index) => (
|
||||
<button
|
||||
@@ -299,18 +299,18 @@ export default function MusicPlayer({ album, isPurchased, onClose, onPurchase }:
|
||||
setIsPlaying(false);
|
||||
setCurrentTime(0);
|
||||
}}
|
||||
className={`w-full text-left px-3 py-2 transition-all border-2 ${
|
||||
className={`w-full text-left px-2 py-1.5 transition-all border ${
|
||||
index === currentSongIndex
|
||||
? 'bg-paper-brown text-paper-light border-paper-dark'
|
||||
: 'hover:bg-paper-sand text-paper-dark border-transparent'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-paper-gray font-mono w-5">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs text-paper-gray font-mono w-4">
|
||||
{String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<span className="text-sm font-medium">{song.title}</span>
|
||||
<span className="text-xs font-medium">{song.title}</span>
|
||||
</div>
|
||||
<span className="text-xs text-paper-gray font-mono">{song.duration}</span>
|
||||
</div>
|
||||
|
||||
+161
-74
@@ -2,10 +2,12 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { FaTimes, FaCreditCard, FaLock } from 'react-icons/fa';
|
||||
import { FaTimes, FaCreditCard, FaLock, FaUniversity } from 'react-icons/fa';
|
||||
import { Album } from '@/lib/types';
|
||||
import { formatPrice } from '@/lib/utils';
|
||||
|
||||
type PaymentMethod = 'ipg' | 'card-to-card';
|
||||
|
||||
interface PaymentModalProps {
|
||||
album: Album | null;
|
||||
onClose: () => void;
|
||||
@@ -13,6 +15,7 @@ interface PaymentModalProps {
|
||||
}
|
||||
|
||||
export default function PaymentModal({ album, onClose, onSuccess }: PaymentModalProps) {
|
||||
const [paymentMethod, setPaymentMethod] = useState<PaymentMethod>('ipg');
|
||||
const [cardNumber, setCardNumber] = useState('');
|
||||
const [cardName, setCardName] = useState('');
|
||||
const [phoneNumber, setPhoneNumber] = useState('');
|
||||
@@ -28,10 +31,22 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
};
|
||||
|
||||
const formatPhoneNumber = (value: string) => {
|
||||
const numbers = value.replace(/\D/g, '');
|
||||
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)}`;
|
||||
// Remove all non-digits
|
||||
let numbers = value.replace(/\D/g, '');
|
||||
|
||||
// Remove leading 98 if user types it (we'll add +98 prefix)
|
||||
if (numbers.startsWith('98')) {
|
||||
numbers = numbers.slice(2);
|
||||
}
|
||||
|
||||
// Limit to 10 digits (Iranian mobile number length)
|
||||
numbers = numbers.slice(0, 10);
|
||||
|
||||
// Format as +98 XXXX XXX XXXX
|
||||
if (numbers.length === 0) return '';
|
||||
if (numbers.length <= 4) return `+98 ${numbers}`;
|
||||
if (numbers.length <= 7) return `+98 ${numbers.slice(0, 4)} ${numbers.slice(4)}`;
|
||||
return `+98 ${numbers.slice(0, 4)} ${numbers.slice(4, 7)} ${numbers.slice(7)}`;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
@@ -39,18 +54,17 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
setError('');
|
||||
|
||||
// Basic validation
|
||||
if (cardNumber.replace(/\s/g, '').length !== 16) {
|
||||
setError('Invalid card number');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cardName.trim()) {
|
||||
setError('Name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
if (phoneNumber.replace(/\D/g, '').length < 10) {
|
||||
setError('Invalid phone number');
|
||||
// Validate Iranian phone number (should have 10 digits after +98)
|
||||
const phoneDigits = phoneNumber.replace(/\D/g, '');
|
||||
// Remove the country code (98) if present to get local number
|
||||
const localNumber = phoneDigits.startsWith('98') ? phoneDigits.slice(2) : phoneDigits;
|
||||
if (localNumber.length !== 10 || !localNumber.startsWith('9')) {
|
||||
setError('Invalid phone number. Must start with 9 and have 10 digits');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,9 +74,16 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
return;
|
||||
}
|
||||
|
||||
if (!txReceipt.trim()) {
|
||||
setError('Transaction receipt is required');
|
||||
return;
|
||||
// Validate based on payment method
|
||||
if (paymentMethod === 'card-to-card') {
|
||||
if (cardNumber.replace(/\s/g, '').length !== 16) {
|
||||
setError('Invalid card number');
|
||||
return;
|
||||
}
|
||||
if (!txReceipt.trim()) {
|
||||
setError('Transaction receipt is required');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!album) return;
|
||||
@@ -70,10 +91,35 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
setProcessing(true);
|
||||
|
||||
try {
|
||||
// Use the provided transaction receipt as the ID
|
||||
// Handle IPG payment
|
||||
if (paymentMethod === 'ipg') {
|
||||
const response = await fetch('/api/payment/initiate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
albumId: album.id,
|
||||
amount: album.price,
|
||||
customerName: cardName.trim(),
|
||||
email: email.trim(),
|
||||
phoneNumber: phoneNumber,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || 'Failed to initiate payment');
|
||||
}
|
||||
|
||||
// Redirect to ZarinPal payment gateway
|
||||
window.location.href = data.paymentUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle card-to-card payment
|
||||
const transactionId = txReceipt.trim() || 'TXN-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
|
||||
// Create purchase via API
|
||||
// Create purchase via API (pending approval for card-to-card)
|
||||
const response = await fetch('/api/purchases', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -85,6 +131,8 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
phoneNumber: phoneNumber,
|
||||
txReceipt: txReceipt.trim(),
|
||||
purchaseDate: new Date().getTime(),
|
||||
approvalStatus: 'pending',
|
||||
paymentMethod: 'card-to-card',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -110,7 +158,7 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
className="fixed inset-0 bg-paper-dark/90 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<motion.div
|
||||
@@ -118,53 +166,90 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
animate={{ scale: 1, opacity: 1 }}
|
||||
exit={{ scale: 0.9, opacity: 0 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="glass-effect rounded-2xl max-w-md w-full p-8 border-2 border-accent-cyan/30"
|
||||
className="paper-card max-w-md w-full p-6 border-4 border-paper-dark shadow-paper-lg"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-2xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent-cyan to-accent-orange">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-2xl font-bold text-paper-dark border-b-4 border-paper-dark inline-block pb-1">
|
||||
Purchase Album
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-white/10 rounded-full transition-colors"
|
||||
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-paper-brown transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<FaTimes className="text-xl text-gray-400" />
|
||||
<FaTimes className="text-lg text-paper-dark" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Album Info */}
|
||||
<div className="mb-6 p-4 bg-white/5 rounded-lg">
|
||||
<h3 className="font-semibold text-white">{album.title}</h3>
|
||||
<p className="text-sm text-gray-400">{album.songs.length} tracks • {album.genre}</p>
|
||||
<p className="text-2xl font-bold text-accent-orange mt-2">{formatPrice(album.price)}</p>
|
||||
<div className="mb-4 p-3 bg-paper-light border-2 border-paper-brown">
|
||||
<h3 className="font-semibold text-paper-dark">{album.title}</h3>
|
||||
<p className="text-sm text-paper-gray">{album.songs.length} tracks • {album.genre}</p>
|
||||
<p className="text-xl font-bold text-paper-brown mt-1">{formatPrice(album.price)}</p>
|
||||
</div>
|
||||
|
||||
{/* Payment Method Selector */}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-paper-dark mb-2">
|
||||
Payment Method / روش پرداخت
|
||||
</label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPaymentMethod('ipg')}
|
||||
className={`p-3 border-2 transition-all ${
|
||||
paymentMethod === 'ipg'
|
||||
? 'bg-paper-brown text-paper-light border-paper-dark shadow-paper'
|
||||
: 'bg-paper-light text-paper-dark border-paper-brown hover:bg-paper-sand'
|
||||
}`}
|
||||
>
|
||||
<FaUniversity className="text-lg mx-auto mb-1" />
|
||||
<div className="text-xs font-medium">IPG</div>
|
||||
<div className="text-xs">درگاه پرداخت</div>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setPaymentMethod('card-to-card')}
|
||||
className={`p-3 border-2 transition-all ${
|
||||
paymentMethod === 'card-to-card'
|
||||
? 'bg-paper-brown text-paper-light border-paper-dark shadow-paper'
|
||||
: 'bg-paper-light text-paper-dark border-paper-brown hover:bg-paper-sand'
|
||||
}`}
|
||||
>
|
||||
<FaCreditCard className="text-lg mx-auto mb-1" />
|
||||
<div className="text-xs font-medium">Card to Card</div>
|
||||
<div className="text-xs">کارت به کارت</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Payment Form */}
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
{/* Card Number */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<FaCreditCard className="inline mr-2" />
|
||||
Card Number
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={cardNumber}
|
||||
onChange={(e) => {
|
||||
const formatted = formatCardNumber(e.target.value.replace(/\D/g, '').slice(0, 16));
|
||||
setCardNumber(formatted);
|
||||
}}
|
||||
placeholder="1234 5678 9012 3456"
|
||||
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>
|
||||
<form onSubmit={handleSubmit} className="space-y-3">
|
||||
{/* Show Card Number only for card-to-card */}
|
||||
{paymentMethod === 'card-to-card' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-paper-dark mb-1">
|
||||
<FaCreditCard className="inline mr-2" />
|
||||
Card Number
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={cardNumber}
|
||||
onChange={(e) => {
|
||||
const formatted = formatCardNumber(e.target.value.replace(/\D/g, '').slice(0, 16));
|
||||
setCardNumber(formatted);
|
||||
}}
|
||||
placeholder="1234 5678 9012 3456"
|
||||
className="w-full px-3 py-2 bg-paper-light border-2 border-paper-brown focus:border-paper-dark focus:outline-none text-paper-dark placeholder-paper-gray shadow-paper text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Card Name */}
|
||||
{/* Full Name */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<label className="block text-sm font-medium text-paper-dark mb-1">
|
||||
Full Name
|
||||
</label>
|
||||
<input
|
||||
@@ -172,15 +257,15 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
value={cardName}
|
||||
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"
|
||||
className="w-full px-3 py-2 bg-paper-light border-2 border-paper-brown focus:border-paper-dark focus:outline-none text-paper-dark placeholder-paper-gray shadow-paper text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Phone Number */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
Phone Number
|
||||
<label className="block text-sm font-medium text-paper-dark mb-1">
|
||||
Phone Number / شماره تماس
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
@@ -189,15 +274,15 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
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"
|
||||
placeholder="+98 9390 084 053"
|
||||
className="w-full px-3 py-2 bg-paper-light border-2 border-paper-brown focus:border-paper-dark focus:outline-none text-paper-dark placeholder-paper-gray shadow-paper text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Email */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-300 mb-2">
|
||||
<label className="block text-sm font-medium text-paper-dark mb-1">
|
||||
Email Address
|
||||
</label>
|
||||
<input
|
||||
@@ -205,32 +290,34 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
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"
|
||||
className="w-full px-3 py-2 bg-paper-light border-2 border-paper-brown focus:border-paper-dark focus:outline-none text-paper-dark placeholder-paper-gray shadow-paper text-sm"
|
||||
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>
|
||||
{/* Transaction Receipt - only for card-to-card */}
|
||||
{paymentMethod === 'card-to-card' && (
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-paper-dark mb-1">
|
||||
Transaction Receipt / رسید پرداخت
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={txReceipt}
|
||||
onChange={(e) => setTxReceipt(e.target.value)}
|
||||
placeholder="TXN-123456789"
|
||||
className="w-full px-3 py-2 bg-paper-light border-2 border-paper-brown focus:border-paper-dark focus:outline-none text-paper-dark placeholder-paper-gray shadow-paper text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error Message */}
|
||||
{error && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
className="p-3 bg-red-500/20 border border-red-500/50 rounded-lg text-red-300 text-sm"
|
||||
className="p-2 bg-red-100 border-2 border-red-400 text-red-700 text-sm"
|
||||
>
|
||||
{error}
|
||||
</motion.div>
|
||||
@@ -240,23 +327,23 @@ export default function PaymentModal({ album, onClose, onSuccess }: PaymentModal
|
||||
<button
|
||||
type="submit"
|
||||
disabled={processing}
|
||||
className="w-full py-4 bg-gradient-to-r from-accent-orange to-accent-orange/80 hover:from-accent-orange/90 hover:to-accent-orange/70 rounded-lg font-semibold text-white transition-all glow-orange disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
className="w-full py-3 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light transition-all shadow-paper-lg disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 text-sm"
|
||||
>
|
||||
{processing ? (
|
||||
<>
|
||||
<div className="animate-spin rounded-full h-5 w-5 border-b-2 border-white"></div>
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-paper-light"></div>
|
||||
Processing...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<FaLock />
|
||||
Complete Purchase - {formatPrice(album.price)}
|
||||
{paymentMethod === 'ipg' ? 'Proceed to Payment Gateway' : `Complete Purchase - ${formatPrice(album.price)}`}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{/* Security Notice */}
|
||||
<p className="text-xs text-gray-500 text-center mt-4">
|
||||
<p className="text-xs text-paper-gray text-center mt-2">
|
||||
<FaLock className="inline mr-1" />
|
||||
This is a demo payment form. No real charges will be made.
|
||||
</p>
|
||||
|
||||
@@ -18,12 +18,12 @@ export default function PurchaseSuccessModal({ show, album, purchase, onClose }:
|
||||
const handleDownloadReceipt = () => {
|
||||
// Create a simple text receipt
|
||||
const receipt = `
|
||||
PURCHASE RECEIPT
|
||||
PURCHASE RECEIPT / رسید خرید
|
||||
================
|
||||
|
||||
Status: PENDING APPROVAL
|
||||
Status: PENDING APPROVAL / در انتظار تایید
|
||||
Album: ${album.title}
|
||||
Artist: Parsa
|
||||
Artist: Parsa Sadatie (@parsadat)
|
||||
Amount: ${formatPrice(album.price)}
|
||||
Transaction ID: ${purchase.transactionId}
|
||||
Date: ${new Date(purchase.purchaseDate).toLocaleString()}
|
||||
@@ -31,9 +31,11 @@ Date: ${new Date(purchase.purchaseDate).toLocaleString()}
|
||||
Tracks Included:
|
||||
${album.songs.map((song, idx) => `${idx + 1}. ${song.title} (${song.duration})`).join('\n')}
|
||||
|
||||
Thank you for your purchase!
|
||||
Thank you for your purchase! / از خرید شما متشکریم!
|
||||
Your order is pending admin approval.
|
||||
سفارش شما در انتظار تایید مدیر است.
|
||||
You will receive access to this album after confirmation.
|
||||
پس از تایید به آلبوم دسترسی خواهید داشت.
|
||||
`;
|
||||
|
||||
const blob = new Blob([receipt], { type: 'text/plain' });
|
||||
@@ -54,7 +56,7 @@ You will receive access to this album after confirmation.
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
className="fixed inset-0 bg-paper-dark/90 backdrop-blur-sm z-50 flex items-center justify-center p-4"
|
||||
onClick={onClose}
|
||||
>
|
||||
<motion.div
|
||||
@@ -62,55 +64,55 @@ You will receive access to this album after confirmation.
|
||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||
exit={{ scale: 0.9, opacity: 0, y: 20 }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="glass-effect rounded-2xl max-w-lg w-full p-8 border-2 border-accent-cyan/30"
|
||||
className="paper-card max-w-lg w-full p-8 border-4 border-paper-dark shadow-paper-lg"
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-3 bg-accent-orange/20 rounded-full">
|
||||
<FaClock className="text-3xl text-accent-orange" />
|
||||
<div className="p-3 bg-paper-brown border-2 border-paper-dark">
|
||||
<FaClock className="text-3xl text-paper-light" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white">
|
||||
<h2 className="text-2xl font-bold text-paper-dark border-b-4 border-paper-dark inline-block pb-1">
|
||||
Purchase Pending Approval
|
||||
</h2>
|
||||
<p className="text-sm text-gray-400">Please wait for admin confirmation</p>
|
||||
<p className="text-sm text-paper-gray mt-2">در انتظار تایید مدیر</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 hover:bg-white/10 rounded-full transition-colors"
|
||||
className="p-2 hover:bg-paper-brown hover:text-paper-light border-2 border-paper-brown transition-colors"
|
||||
aria-label="Close"
|
||||
>
|
||||
<FaTimes className="text-xl text-gray-400" />
|
||||
<FaTimes className="text-xl text-paper-dark" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Receipt Details */}
|
||||
<div className="space-y-4 mb-6">
|
||||
<div className="p-4 bg-white/5 rounded-lg space-y-3">
|
||||
<div className="p-4 bg-paper-light border-2 border-paper-brown space-y-3">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Album</span>
|
||||
<span className="text-white font-semibold">{album.title}</span>
|
||||
<span className="text-paper-gray">Album / آلبوم</span>
|
||||
<span className="text-paper-dark font-semibold">{album.title}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Artist</span>
|
||||
<span className="text-white font-semibold">Parsa</span>
|
||||
<span className="text-paper-gray">Artist / هنرمند</span>
|
||||
<span className="text-paper-dark font-semibold">Parsa Sadatie</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-400">Tracks</span>
|
||||
<span className="text-white font-semibold">{album.songs.length} songs</span>
|
||||
<span className="text-paper-gray">Tracks / تعداد آهنگ</span>
|
||||
<span className="text-paper-dark font-semibold">{album.songs.length} songs</span>
|
||||
</div>
|
||||
<div className="border-t border-white/10 pt-3 flex justify-between">
|
||||
<span className="text-gray-400">Amount</span>
|
||||
<span className="text-accent-orange font-bold text-xl">{formatPrice(album.price)}</span>
|
||||
<div className="border-t-2 border-paper-brown pt-3 flex justify-between">
|
||||
<span className="text-paper-gray">Amount / مبلغ</span>
|
||||
<span className="text-paper-brown font-bold text-xl">{formatPrice(album.price)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 bg-accent-cyan/10 border border-accent-cyan/30 rounded-lg">
|
||||
<p className="text-xs text-gray-400 mb-1">Transaction ID</p>
|
||||
<p className="text-sm font-mono text-accent-cyan">{purchase.transactionId}</p>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
<div className="p-4 bg-paper-sand border-2 border-paper-dark">
|
||||
<p className="text-xs text-paper-gray mb-1">Transaction ID / شناسه تراکنش</p>
|
||||
<p className="text-sm font-mono text-paper-dark font-semibold">{purchase.transactionId}</p>
|
||||
<p className="text-xs text-paper-brown mt-2">
|
||||
{new Date(purchase.purchaseDate).toLocaleString()}
|
||||
</p>
|
||||
</div>
|
||||
@@ -120,25 +122,26 @@ You will receive access to this album after confirmation.
|
||||
<div className="space-y-3">
|
||||
<button
|
||||
onClick={handleDownloadReceipt}
|
||||
className="w-full py-3 bg-white/10 hover:bg-white/20 border border-white/20 rounded-lg font-semibold text-white transition-all flex items-center justify-center gap-2"
|
||||
className="w-full py-3 bg-paper-light hover:bg-paper-sand border-2 border-paper-brown font-semibold text-paper-dark transition-all shadow-paper flex items-center justify-center gap-2"
|
||||
>
|
||||
<FaDownload />
|
||||
Download Receipt
|
||||
Download Receipt / دریافت رسید
|
||||
</button>
|
||||
|
||||
<motion.button
|
||||
whileHover={{ scale: 1.02 }}
|
||||
whileTap={{ scale: 0.98 }}
|
||||
onClick={onClose}
|
||||
className="w-full py-3 bg-gradient-to-r from-accent-cyan to-accent-cyan/80 hover:from-accent-cyan/90 hover:to-accent-cyan/70 rounded-lg font-semibold text-white transition-all glow-cyan"
|
||||
className="w-full py-3 bg-paper-brown hover:bg-paper-dark border-2 border-paper-dark font-semibold text-paper-light transition-all shadow-paper-lg"
|
||||
>
|
||||
OK, Got It
|
||||
OK, Got It / متوجه شدم
|
||||
</motion.button>
|
||||
</div>
|
||||
|
||||
{/* Info */}
|
||||
<p className="text-xs text-gray-500 text-center mt-6">
|
||||
Your purchase will be reviewed by an admin. You will receive access after approval.
|
||||
<p className="text-xs text-paper-gray text-center mt-6">
|
||||
Your purchase will be reviewed by an admin. You will receive access after approval.<br />
|
||||
سفارش شما توسط مدیر بررسی میشود. پس از تایید به آلبوم دسترسی خواهید داشت.
|
||||
</p>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
|
||||
Reference in New Issue
Block a user