Files
gridpilot.gg/apps/website/components/leagues/JoinLeagueButton.tsx
2025-12-18 14:30:19 +01:00

174 lines
5.2 KiB
TypeScript

'use client';
import { useEffectiveDriverId } from '@/hooks/useEffectiveDriverId';
import { getMembership } from '@/lib/leagueMembership';
import { useState } from 'react';
import Button from '../ui/Button';
interface JoinLeagueButtonProps {
leagueId: string;
isInviteOnly?: boolean;
onMembershipChange?: () => void;
}
export default function JoinLeagueButton({
leagueId,
isInviteOnly = false,
onMembershipChange,
}: JoinLeagueButtonProps) {
const currentDriverId = useEffectiveDriverId();
const membership = getMembership(leagueId, currentDriverId);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [dialogAction, setDialogAction] = useState<'join' | 'leave' | 'request'>('join');
const handleJoin = async () => {
setLoading(true);
setError(null);
try {
const membershipRepo = getLeagueMembershipRepository();
if (isInviteOnly) {
const existing = await membershipRepo.getMembership(leagueId, currentDriverId);
if (existing) {
throw new Error('Already a member or have a pending request');
}
throw new Error(
'Requesting to join invite-only leagues is not available in this alpha build.',
);
}
const useCase = getJoinLeagueUseCase();
await useCase.execute({ leagueId, driverId: currentDriverId });
onMembershipChange?.();
setShowConfirmDialog(false);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to join league');
} finally {
setLoading(false);
}
};
const handleLeave = async () => {
setLoading(true);
setError(null);
try {
const membershipRepo = getLeagueMembershipRepository();
const existing = await membershipRepo.getMembership(leagueId, currentDriverId);
if (!existing) {
throw new Error('Not a member of this league');
}
if (existing.role === 'owner') {
throw new Error('League owner cannot leave the league');
}
await membershipRepo.removeMembership(leagueId, currentDriverId);
onMembershipChange?.();
setShowConfirmDialog(false);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to leave league');
} finally {
setLoading(false);
}
};
const openDialog = (action: 'join' | 'leave' | 'request') => {
setDialogAction(action);
setShowConfirmDialog(true);
setError(null);
};
const closeDialog = () => {
setShowConfirmDialog(false);
setError(null);
};
const getButtonText = (): string => {
if (!membership) {
return isInviteOnly ? 'Request to Join' : 'Join League';
}
if (membership.role === 'owner') {
return 'League Owner';
}
return 'Leave League';
};
const getButtonVariant = (): 'primary' | 'secondary' | 'danger' => {
if (!membership) return 'primary';
if (membership.role === 'owner') return 'secondary';
return 'danger';
};
const isDisabled = membership?.role === 'owner' || loading;
return (
<>
<Button
variant={getButtonVariant()}
onClick={() => {
if (membership) {
openDialog('leave');
} else {
openDialog(isInviteOnly ? 'request' : 'join');
}
}}
disabled={isDisabled}
className="w-full"
>
{loading ? 'Processing...' : getButtonText()}
</Button>
{error && (
<p className="mt-2 text-sm text-red-400">{error}</p>
)}
{/* Confirmation Dialog */}
{showConfirmDialog && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-iron-gray border border-charcoal-outline rounded-lg max-w-md w-full p-6">
<h3 className="text-xl font-semibold text-white mb-4">
{dialogAction === 'leave' ? 'Leave League' : dialogAction === 'request' ? 'Request to Join' : 'Join League'}
</h3>
<p className="text-gray-400 mb-6">
{dialogAction === 'leave'
? 'Are you sure you want to leave this league? You can rejoin later.'
: dialogAction === 'request'
? 'Your join request will be sent to the league admins for approval.'
: 'Are you sure you want to join this league?'}
</p>
{error && (
<div className="mb-4 p-3 rounded bg-red-500/10 border border-red-500/30 text-red-400 text-sm">
{error}
</div>
)}
<div className="flex gap-3">
<Button
variant={dialogAction === 'leave' ? 'danger' : 'primary'}
onClick={dialogAction === 'leave' ? handleLeave : handleJoin}
disabled={loading}
className="flex-1"
>
{loading ? 'Processing...' : 'Confirm'}
</Button>
<Button
variant="secondary"
onClick={closeDialog}
disabled={loading}
className="flex-1"
>
Cancel
</Button>
</div>
</div>
</div>
)}
</>
);
}