Basic frontend
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { membershipService, paymentService, MembershipTier, MembershipCreateData, PaymentCreateData } from '../services/membershipService';
|
||||
|
||||
interface MembershipSetupProps {
|
||||
onMembershipCreated: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const MembershipSetup: React.FC<MembershipSetupProps> = ({ onMembershipCreated, onCancel }) => {
|
||||
const [tiers, setTiers] = useState<MembershipTier[]>([]);
|
||||
const [selectedTier, setSelectedTier] = useState<MembershipTier | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [step, setStep] = useState<'select' | 'payment' | 'confirm'>('select');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
loadTiers();
|
||||
}, []);
|
||||
|
||||
const loadTiers = async () => {
|
||||
try {
|
||||
const tierData = await membershipService.getTiers();
|
||||
setTiers(tierData);
|
||||
} catch (error) {
|
||||
console.error('Failed to load tiers:', error);
|
||||
setError('Failed to load membership tiers');
|
||||
}
|
||||
};
|
||||
|
||||
const handleTierSelect = (tier: MembershipTier) => {
|
||||
setSelectedTier(tier);
|
||||
setStep('payment');
|
||||
};
|
||||
|
||||
const handlePayment = async () => {
|
||||
if (!selectedTier) return;
|
||||
|
||||
setLoading(true);
|
||||
setError('');
|
||||
|
||||
try {
|
||||
// Calculate dates (start today, end one year from now)
|
||||
const startDate = new Date().toISOString().split('T')[0];
|
||||
const endDate = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
|
||||
|
||||
// Create membership
|
||||
const membershipData: MembershipCreateData = {
|
||||
tier_id: selectedTier.id,
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
auto_renew: false
|
||||
};
|
||||
|
||||
const membership = await membershipService.createMembership(membershipData);
|
||||
|
||||
// Create fake payment
|
||||
const paymentData: PaymentCreateData = {
|
||||
amount: selectedTier.annual_fee,
|
||||
payment_method: 'dummy',
|
||||
membership_id: membership.id,
|
||||
notes: `Fake payment for ${selectedTier.name} membership`
|
||||
};
|
||||
|
||||
await paymentService.createPayment(paymentData);
|
||||
|
||||
setStep('confirm');
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.detail || 'Failed to create membership');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
onMembershipCreated();
|
||||
};
|
||||
|
||||
if (step === 'select') {
|
||||
return (
|
||||
<div className="card">
|
||||
<h3 style={{ marginBottom: '16px' }}>Choose Your Membership</h3>
|
||||
{error && <div className="alert alert-error">{error}</div>}
|
||||
|
||||
<div style={{ display: 'grid', gap: '16px' }}>
|
||||
{tiers.map(tier => (
|
||||
<div
|
||||
key={tier.id}
|
||||
style={{
|
||||
border: '1px solid #ddd',
|
||||
borderRadius: '8px',
|
||||
padding: '16px',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.borderColor = '#0066cc';
|
||||
e.currentTarget.style.boxShadow = '0 2px 8px rgba(0, 102, 204, 0.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.borderColor = '#ddd';
|
||||
e.currentTarget.style.boxShadow = 'none';
|
||||
}}
|
||||
onClick={() => handleTierSelect(tier)}
|
||||
>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
|
||||
<h4 style={{ margin: 0, color: '#0066cc' }}>{tier.name}</h4>
|
||||
<span style={{ fontSize: '18px', fontWeight: 'bold', color: '#0066cc' }}>
|
||||
£{tier.annual_fee.toFixed(2)}/year
|
||||
</span>
|
||||
</div>
|
||||
<p style={{ margin: '8px 0', color: '#666', fontSize: '14px' }}>{tier.description}</p>
|
||||
<div style={{ backgroundColor: '#f5f5f5', padding: '12px', borderRadius: '4px' }}>
|
||||
<strong>Benefits:</strong>
|
||||
<p style={{ marginTop: '4px', fontSize: '14px' }}>{tier.benefits}</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div style={{ marginTop: '20px', textAlign: 'center' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={onCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === 'payment') {
|
||||
return (
|
||||
<div className="card">
|
||||
<h3 style={{ marginBottom: '16px' }}>Complete Payment</h3>
|
||||
{error && <div className="alert alert-error">{error}</div>}
|
||||
|
||||
{selectedTier && (
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<h4>Selected Membership: {selectedTier.name}</h4>
|
||||
<p><strong>Annual Fee:</strong> £{selectedTier.annual_fee.toFixed(2)}</p>
|
||||
<p><strong>Benefits:</strong> {selectedTier.benefits}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ backgroundColor: '#fff3cd', border: '1px solid #ffeaa7', borderRadius: '4px', padding: '16px', marginBottom: '20px' }}>
|
||||
<strong>Demo Payment</strong>
|
||||
<p style={{ marginTop: '8px', marginBottom: 0 }}>
|
||||
This is a fake payment flow for demonstration purposes. In a real application, you would integrate with a payment processor like Stripe or Square.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={handlePayment}
|
||||
disabled={loading}
|
||||
style={{ marginRight: '10px' }}
|
||||
>
|
||||
{loading ? 'Processing...' : 'Complete Fake Payment'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={() => setStep('select')}
|
||||
disabled={loading}
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (step === 'confirm') {
|
||||
return (
|
||||
<div className="card">
|
||||
<h3 style={{ marginBottom: '16px' }}>Membership Created Successfully!</h3>
|
||||
|
||||
{selectedTier && (
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<h4>Your Membership Details:</h4>
|
||||
<p><strong>Tier:</strong> {selectedTier.name}</p>
|
||||
<p><strong>Annual Fee:</strong> £{selectedTier.annual_fee.toFixed(2)}</p>
|
||||
<p><strong>Status:</strong> <span className="status-badge status-pending">Pending</span></p>
|
||||
<p style={{ fontSize: '14px', color: '#666', marginTop: '12px' }}>
|
||||
Your membership application has been submitted. An administrator will review and activate your membership shortly.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary"
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
Return to Dashboard
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default MembershipSetup;
|
||||
Reference in New Issue
Block a user