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
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>
|
|
);
|
|
}
|