You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
7.4 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import Modal from '@/src/components/modals/Modal';
import ModalHeader from '@/src/components/modals/ModalHeader';
import ModalBody from '@/src/components/modals/ModalBody';
import ModalFooter from '@/src/components/modals/ModalFooter';
import { addMember, listPlans } from '@/src/lib/api/facility-admin';
import type { AddMemberRequest, MemberRole, MemberStatus, MembershipPlan } from '@/src/types/facility-admin';
interface AddMemberModalProps {
isOpen: boolean;
onClose: () => void;
facilityId: number;
onSuccess: () => void;
}
export default function AddMemberModal({
isOpen,
onClose,
facilityId,
onSuccess
}: AddMemberModalProps) {
const [plans, setPlans] = useState<MembershipPlan[]>([]);
const [formData, setFormData] = useState<AddMemberRequest>({
app_user_id: 0,
role: 'member',
facility_membership_plan_id: null,
sport_id: null,
status: 'active',
starts_at: new Date().toISOString().split('T')[0],
ends_at: null
});
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
fetchPlans();
}, [facilityId]);
async function fetchPlans() {
const result = await listPlans(facilityId, { include_inactive: false });
if (result.success) {
setPlans(result.data);
}
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setSubmitting(true);
setError(null);
if (formData.app_user_id === 0) {
setError('Please enter a valid user ID');
setSubmitting(false);
return;
}
const result = await addMember(facilityId, formData);
setSubmitting(false);
if (result.success) {
onSuccess();
onClose();
} else {
setError(result.error.detail || 'Failed to add member');
}
}
return (
<Modal isOpen={isOpen} onClose={onClose} size="md">
<form onSubmit={handleSubmit}>
<ModalHeader onClose={onClose}>Add Member</ModalHeader>
<ModalBody>
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg mb-4">
{error}
</div>
)}
<div className="space-y-4">
{/* User ID (simplified - in production, use email lookup) */}
<div>
<label htmlFor="app_user_id" className="block text-sm font-medium text-gray-700 mb-1">
User ID <span className="text-red-500">*</span>
</label>
<input
type="number"
id="app_user_id"
value={formData.app_user_id || ''}
onChange={e => setFormData({ ...formData, app_user_id: parseInt(e.target.value) || 0 })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
placeholder="Enter user ID"
required
/>
<p className="text-xs text-gray-500 mt-1">Enter the app_user_id of the user to add</p>
</div>
{/* Role */}
<div>
<label htmlFor="role" className="block text-sm font-medium text-gray-700 mb-1">
Role <span className="text-red-500">*</span>
</label>
<select
id="role"
value={formData.role}
onChange={e => setFormData({ ...formData, role: e.target.value as MemberRole })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
required
>
<option value="guest">Guest</option>
<option value="member">Member</option>
<option value="staff">Staff</option>
<option value="admin">Admin</option>
</select>
</div>
{/* Membership Plan */}
<div>
<label htmlFor="plan" className="block text-sm font-medium text-gray-700 mb-1">
Membership Plan {formData.role === 'member' && <span className="text-red-500">*</span>}
</label>
<select
id="plan"
value={formData.facility_membership_plan_id || ''}
onChange={e => setFormData({ ...formData, facility_membership_plan_id: parseInt(e.target.value) || null })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
required={formData.role === 'member'}
>
<option value="">No Plan</option>
{plans.map(plan => (
<option key={plan.facility_membership_plan_id} value={plan.facility_membership_plan_id}>
{plan.name} - CHF {(plan.price_cents / 100).toFixed(2)} / {plan.billing_period}
</option>
))}
</select>
</div>
{/* Status */}
<div>
<label htmlFor="status" className="block text-sm font-medium text-gray-700 mb-1">
Status
</label>
<select
id="status"
value={formData.status}
onChange={e => setFormData({ ...formData, status: e.target.value as MemberStatus })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
>
<option value="active">Active</option>
<option value="trial">Trial</option>
<option value="grace">Grace Period</option>
<option value="past_due">Past Due</option>
<option value="paused">Paused</option>
</select>
</div>
{/* End Date */}
<div>
<label htmlFor="ends_at" className="block text-sm font-medium text-gray-700 mb-1">
End Date (Optional)
</label>
<input
type="date"
id="ends_at"
value={formData.ends_at || ''}
onChange={e => setFormData({ ...formData, ends_at: e.target.value || null })}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
<p className="text-xs text-gray-500 mt-1">Leave empty for indefinite membership</p>
</div>
</div>
</ModalBody>
<ModalFooter>
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-gray-600 hover:text-gray-800 font-medium transition-colors"
disabled={submitting}
>
Cancel
</button>
<button
type="submit"
disabled={submitting}
className="bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-700 hover:to-indigo-700 disabled:opacity-50 disabled:cursor-not-allowed text-white px-6 py-2 rounded-lg font-semibold shadow-md transition-all duration-200"
>
{submitting ? 'Adding...' : 'Add Member'}
</button>
</ModalFooter>
</form>
</Modal>
);
}