1. New Components- src/components/PaymentMethodCard.js - Displays individual payment method- src/components/AddPaymentMethodDialog.js - Stripe Elements dialog for adding cards- src/components/PaymentMethodsSection.js - Main payment methods UI- src/components/PasswordConfirmDialog.js - Admin password re-entry dialog- src/components/admin/AdminPaymentMethodsPanel.js - Admin panel for user payment methods2. Profile Integration (src/pages/Profile.js)- Replaced placeholder Payment Method section with PaymentMethodsSection3. Admin Integration (src/pages/admin/AdminUserView.js)- Added AdminPaymentMethodsPanel to user detail view
This commit is contained in:
186
src/components/PaymentMethodCard.js
Normal file
186
src/components/PaymentMethodCard.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import React from 'react';
|
||||
import { CreditCard, Trash2, Star, Banknote, Building2, FileCheck } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
/**
|
||||
* Card brand icon mapping
|
||||
*/
|
||||
const getBrandIcon = (brand) => {
|
||||
const brandLower = brand?.toLowerCase();
|
||||
// Return text abbreviation for known brands
|
||||
switch (brandLower) {
|
||||
case 'visa':
|
||||
return 'VISA';
|
||||
case 'mastercard':
|
||||
return 'MC';
|
||||
case 'amex':
|
||||
case 'american_express':
|
||||
return 'AMEX';
|
||||
case 'discover':
|
||||
return 'DISC';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get icon for payment method type
|
||||
*/
|
||||
const getPaymentTypeIcon = (paymentType) => {
|
||||
switch (paymentType) {
|
||||
case 'cash':
|
||||
return Banknote;
|
||||
case 'bank_transfer':
|
||||
return Building2;
|
||||
case 'check':
|
||||
return FileCheck;
|
||||
default:
|
||||
return CreditCard;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Format payment type for display
|
||||
*/
|
||||
const formatPaymentType = (paymentType) => {
|
||||
switch (paymentType) {
|
||||
case 'cash':
|
||||
return 'Cash';
|
||||
case 'bank_transfer':
|
||||
return 'Bank Transfer';
|
||||
case 'check':
|
||||
return 'Check';
|
||||
case 'card':
|
||||
return 'Card';
|
||||
default:
|
||||
return paymentType;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* PaymentMethodCard - Displays a single payment method
|
||||
*/
|
||||
const PaymentMethodCard = ({
|
||||
method,
|
||||
onSetDefault,
|
||||
onDelete,
|
||||
loading = false,
|
||||
showActions = true,
|
||||
}) => {
|
||||
const PaymentIcon = getPaymentTypeIcon(method.payment_type);
|
||||
const brandAbbr = method.card_brand ? getBrandIcon(method.card_brand) : null;
|
||||
const isExpired = method.card_exp_year && method.card_exp_month &&
|
||||
new Date(method.card_exp_year, method.card_exp_month) < new Date();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center justify-between p-4 border rounded-xl ${
|
||||
method.is_default
|
||||
? 'border-brand-purple bg-[var(--lavender-500)]'
|
||||
: 'border-[var(--neutral-800)] bg-white'
|
||||
} ${isExpired ? 'opacity-70' : ''}`}
|
||||
>
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Payment Method Icon */}
|
||||
<div className={`p-3 rounded-full ${
|
||||
method.is_default
|
||||
? 'bg-brand-purple text-white'
|
||||
: 'bg-[var(--lavender-300)] text-brand-purple'
|
||||
}`}>
|
||||
<PaymentIcon className="h-5 w-5" />
|
||||
</div>
|
||||
|
||||
{/* Payment Method Details */}
|
||||
<div>
|
||||
{method.payment_type === 'card' ? (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
{brandAbbr && (
|
||||
<span className="text-xs font-bold text-[var(--purple-ink)] bg-[var(--lavender-300)] px-2 py-0.5 rounded">
|
||||
{brandAbbr}
|
||||
</span>
|
||||
)}
|
||||
<span
|
||||
className="font-medium text-[var(--purple-ink)]"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
{method.card_brand ? method.card_brand.charAt(0).toUpperCase() + method.card_brand.slice(1) : 'Card'} •••• {method.card_last4 || '****'}
|
||||
</span>
|
||||
{method.is_default && (
|
||||
<span className="flex items-center gap-1 text-xs text-brand-purple font-medium">
|
||||
<Star className="h-3 w-3 fill-current" />
|
||||
Default
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p
|
||||
className={`text-sm ${isExpired ? 'text-red-500' : 'text-brand-purple'}`}
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
>
|
||||
{isExpired ? 'Expired' : 'Expires'} {method.card_exp_month?.toString().padStart(2, '0')}/{method.card_exp_year?.toString().slice(-2)}
|
||||
{method.card_funding && (
|
||||
<span className="ml-2 text-xs capitalize">({method.card_funding})</span>
|
||||
)}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="font-medium text-[var(--purple-ink)]"
|
||||
style={{ fontFamily: "'Inter', sans-serif" }}
|
||||
>
|
||||
{formatPaymentType(method.payment_type)}
|
||||
</span>
|
||||
{method.is_default && (
|
||||
<span className="flex items-center gap-1 text-xs text-brand-purple font-medium">
|
||||
<Star className="h-3 w-3 fill-current" />
|
||||
Default
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{method.manual_notes && (
|
||||
<p
|
||||
className="text-sm text-brand-purple"
|
||||
style={{ fontFamily: "'Nunito Sans', sans-serif" }}
|
||||
>
|
||||
{method.manual_notes}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
{showActions && (
|
||||
<div className="flex items-center gap-2">
|
||||
{!method.is_default && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onSetDefault?.(method.id)}
|
||||
disabled={loading}
|
||||
className="border border-brand-purple text-brand-purple hover:bg-[var(--lavender-300)] rounded-lg text-xs px-3"
|
||||
>
|
||||
Set Default
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => onDelete?.(method.id)}
|
||||
disabled={loading}
|
||||
className="border border-red-500 text-red-500 hover:bg-red-50 rounded-lg p-2"
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PaymentMethodCard;
|
||||
Reference in New Issue
Block a user