forked from jamesp/sasa-membership
190 lines
7.8 KiB
TypeScript
190 lines
7.8 KiB
TypeScript
import React from 'react';
|
|
import { Event, Membership, Payment } from '../../services/membershipService';
|
|
import { utcToLondonTimeInput } from '../../utils/timezone';
|
|
|
|
interface MemberOverviewPageProps {
|
|
activeMembership?: Membership;
|
|
formatDate: (dateString: string) => string;
|
|
getStatusClass: (status: string) => string;
|
|
handleMembershipSetup: () => void;
|
|
handleRSVP: (eventId: number, status: 'attending' | 'maybe' | 'not_attending') => void;
|
|
payments: Payment[];
|
|
rsvpLoading: { [eventId: number]: boolean };
|
|
upcomingEvents: Event[];
|
|
}
|
|
|
|
const MemberOverviewPage: React.FC<MemberOverviewPageProps> = ({
|
|
activeMembership,
|
|
formatDate,
|
|
getStatusClass,
|
|
handleMembershipSetup,
|
|
handleRSVP,
|
|
payments,
|
|
rsvpLoading,
|
|
upcomingEvents
|
|
}) => (
|
|
<>
|
|
<section className="member-hero">
|
|
<div>
|
|
<p className="member-hero-kicker">Member Dashboard</p>
|
|
<h2 className="member-hero-title">Everything you need for your SASA membership</h2>
|
|
<p className="member-hero-copy">
|
|
Track your status, respond to upcoming events, and keep your details current from one place.
|
|
</p>
|
|
</div>
|
|
<div className="member-stat-strip">
|
|
<div className="member-stat-chip">
|
|
<span className="member-stat-label">Membership</span>
|
|
<strong className="member-stat-value">{activeMembership ? activeMembership.status : 'Not set up'}</strong>
|
|
</div>
|
|
<div className="member-stat-chip">
|
|
<span className="member-stat-label">Events</span>
|
|
<strong className="member-stat-value">{upcomingEvents.length}</strong>
|
|
</div>
|
|
<div className="member-stat-chip">
|
|
<span className="member-stat-label">Payments</span>
|
|
<strong className="member-stat-value">{payments.length}</strong>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<div className="dashboard-grid member-overview-grid">
|
|
{activeMembership ? (
|
|
<div className="card member-card">
|
|
<div className="member-card-header">
|
|
<div>
|
|
<p className="member-card-kicker">Membership</p>
|
|
<h3>Your Membership</h3>
|
|
</div>
|
|
<span className={`status-badge ${getStatusClass(activeMembership.status)}`}>{activeMembership.status.toUpperCase()}</span>
|
|
</div>
|
|
<h4 className="member-tier-title">{activeMembership.tier.name}</h4>
|
|
<div className="member-data-list">
|
|
<div className="member-data-row"><strong>Membership Number</strong><span>{activeMembership.id}</span></div>
|
|
<div className="member-data-row"><strong>Annual Fee</strong><span>£{activeMembership.tier.annual_fee.toFixed(2)}</span></div>
|
|
<div className="member-data-row"><strong>Valid From</strong><span>{formatDate(activeMembership.start_date)}</span></div>
|
|
<div className="member-data-row"><strong>Valid Until</strong><span>{formatDate(activeMembership.end_date)}</span></div>
|
|
<div className="member-data-row"><strong>Auto Renew</strong><span>{activeMembership.auto_renew ? 'Yes' : 'No'}</span></div>
|
|
</div>
|
|
<div className="member-info-panel">
|
|
<strong>Benefits:</strong>
|
|
<p>{activeMembership.tier.benefits}</p>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="card member-card">
|
|
<div className="member-card-header">
|
|
<div>
|
|
<p className="member-card-kicker">Membership</p>
|
|
<h3>Set Up Your Membership</h3>
|
|
</div>
|
|
</div>
|
|
<p>Choose from our membership tiers to get started with SASA benefits.</p>
|
|
<p className="member-muted-copy">Available tiers include Personal, Aircraft Owners, and Corporate memberships.</p>
|
|
<button className="btn btn-primary member-inline-action" onClick={handleMembershipSetup}>
|
|
Set Up Membership
|
|
</button>
|
|
</div>
|
|
)}
|
|
|
|
<div className="card member-card">
|
|
<div className="member-card-header">
|
|
<div>
|
|
<p className="member-card-kicker">Calendar</p>
|
|
<h3>Upcoming Events</h3>
|
|
</div>
|
|
</div>
|
|
{upcomingEvents.length > 0 ? (
|
|
<div className="events-container">
|
|
{upcomingEvents.map((event) => (
|
|
<div key={event.id} className="event-card">
|
|
<div className="event-header">
|
|
<div className="event-info">
|
|
<h4 className="event-title">{event.title}</h4>
|
|
<p className="event-datetime">
|
|
{formatDate(event.event_date)} at {utcToLondonTimeInput(event.event_date)}
|
|
</p>
|
|
{event.location && <p className="event-location">{event.location}</p>}
|
|
</div>
|
|
<div className="event-rsvp-buttons">
|
|
<button
|
|
className={`rsvp-btn rsvp-btn-attending ${event.rsvp_status === 'attending' ? 'active' : ''}`}
|
|
onClick={() => handleRSVP(event.id, 'attending')}
|
|
disabled={rsvpLoading[event.id]}
|
|
>
|
|
{rsvpLoading[event.id] ? '...' : 'Attending'}
|
|
</button>
|
|
<button
|
|
className={`rsvp-btn rsvp-btn-maybe ${event.rsvp_status === 'maybe' ? 'active' : ''}`}
|
|
onClick={() => handleRSVP(event.id, 'maybe')}
|
|
disabled={rsvpLoading[event.id]}
|
|
>
|
|
{rsvpLoading[event.id] ? '...' : 'Maybe'}
|
|
</button>
|
|
<button
|
|
className={`rsvp-btn rsvp-btn-not-attending ${event.rsvp_status === 'not_attending' ? 'active' : ''}`}
|
|
onClick={() => handleRSVP(event.id, 'not_attending')}
|
|
disabled={rsvpLoading[event.id]}
|
|
>
|
|
{rsvpLoading[event.id] ? '...' : 'Not Attending'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{event.description && <p className="event-description">{event.description}</p>}
|
|
{event.rsvp_status && (
|
|
<div className={`event-rsvp-status ${event.rsvp_status}`}>
|
|
<strong>Your RSVP:</strong> <span className="member-rsvp-state">{event.rsvp_status.replace('_', ' ')}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<p className="member-muted-copy">No upcoming events at this time.</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="card member-card">
|
|
<div className="member-card-header">
|
|
<div>
|
|
<p className="member-card-kicker">Billing</p>
|
|
<h3>Payment History</h3>
|
|
</div>
|
|
</div>
|
|
{payments.length > 0 ? (
|
|
<div className="table-container">
|
|
<table className="member-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Amount</th>
|
|
<th>Method</th>
|
|
<th>Status</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{payments.map((payment) => (
|
|
<tr key={payment.id}>
|
|
<td>{payment.payment_date ? formatDate(payment.payment_date) : 'Pending'}</td>
|
|
<td>£{payment.amount.toFixed(2)}</td>
|
|
<td className="member-table-caps">{payment.payment_method}</td>
|
|
<td>
|
|
<span className={`status-badge ${getStatusClass(payment.status)}`}>
|
|
{payment.status.toUpperCase()}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
) : (
|
|
<p className="member-muted-copy">No payment history available.</p>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
|
|
export default MemberOverviewPage;
|