feat: add destroy team feature
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
useTeamJoinRequests,
|
useTeamJoinRequests,
|
||||||
useRespondToJoinRequest,
|
useRespondToJoinRequest,
|
||||||
useLeaveTeam,
|
useLeaveTeam,
|
||||||
|
useDeleteTeam,
|
||||||
ETeamMemberRole,
|
ETeamMemberRole,
|
||||||
useAuthStore,
|
useAuthStore,
|
||||||
} from '@imphnen-frontend-service/service';
|
} from '@imphnen-frontend-service/service';
|
||||||
@@ -54,6 +55,8 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
const [showInviteModal, setShowInviteModal] = useState(false);
|
const [showInviteModal, setShowInviteModal] = useState(false);
|
||||||
const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false);
|
const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false);
|
||||||
const [showLeaveModal, setShowLeaveModal] = useState(false);
|
const [showLeaveModal, setShowLeaveModal] = useState(false);
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||||
|
const [deleteConfirmText, setDeleteConfirmText] = useState('');
|
||||||
const [inviteEmail, setInviteEmail] = useState('');
|
const [inviteEmail, setInviteEmail] = useState('');
|
||||||
const [imagesLoaded, setImagesLoaded] = useState(false);
|
const [imagesLoaded, setImagesLoaded] = useState(false);
|
||||||
const [imageLoadCount, setImageLoadCount] = useState(0);
|
const [imageLoadCount, setImageLoadCount] = useState(0);
|
||||||
@@ -104,6 +107,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
const { mutateAsync: respondToJoinRequest, isPending: isResponding } =
|
const { mutateAsync: respondToJoinRequest, isPending: isResponding } =
|
||||||
useRespondToJoinRequest(teamId || '');
|
useRespondToJoinRequest(teamId || '');
|
||||||
const { mutateAsync: leaveTeam, isPending: isLeaving } = useLeaveTeam();
|
const { mutateAsync: leaveTeam, isPending: isLeaving } = useLeaveTeam();
|
||||||
|
const { mutateAsync: deleteTeam, isPending: isDeleting } = useDeleteTeam();
|
||||||
|
|
||||||
const joinRequests = joinRequestsData?.data || [];
|
const joinRequests = joinRequestsData?.data || [];
|
||||||
const pendingJoinRequests = joinRequests.filter(
|
const pendingJoinRequests = joinRequests.filter(
|
||||||
@@ -157,6 +161,19 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDeleteTeam = async () => {
|
||||||
|
if (!teamId) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteTeam(teamId);
|
||||||
|
toast.success('Team deleted successfully');
|
||||||
|
navigate('/dashboard');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Failed to delete team:', error);
|
||||||
|
toast.error(error?.message || 'Failed to delete team');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isLoadingTeam || isLoadingMembers) {
|
if (isLoadingTeam || isLoadingMembers) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-950">
|
||||||
@@ -408,6 +425,21 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{/* Danger Zone - Only show when leader is alone */}
|
||||||
|
{members.length === 1 && (
|
||||||
|
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
|
<h3 className="text-sm font-medium text-red-600 dark:text-red-400 mb-3">
|
||||||
|
Danger Zone
|
||||||
|
</h3>
|
||||||
|
<Button
|
||||||
|
className="w-full bg-red-600 hover:bg-red-700 text-white"
|
||||||
|
onClick={() => setShowDeleteModal(true)}
|
||||||
|
>
|
||||||
|
<Icon icon="mdi:delete" className="inline-block mr-2" />
|
||||||
|
Delete Team
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -767,6 +799,66 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Delete Team Confirmation Modal */}
|
||||||
|
{showDeleteModal && (
|
||||||
|
<div className="fixed inset-0 bg-black/30 dark:bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
||||||
|
<div className="bg-white dark:bg-gray-900 rounded-lg shadow-xl max-w-md w-full p-6">
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<Icon
|
||||||
|
icon="mdi:delete-alert"
|
||||||
|
className="text-3xl text-red-600 dark:text-red-400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-2">
|
||||||
|
Delete Team?
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
This action is <strong className="text-red-600 dark:text-red-400">permanent</strong> and cannot be undone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4 mb-6">
|
||||||
|
<p className="text-sm text-red-800 dark:text-red-300 font-sans">
|
||||||
|
<strong>Warning:</strong> All team data, chat messages, and submissions will be permanently deleted.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-6">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
Type <span className="font-bold text-red-600 dark:text-red-400">{team?.name}</span> to confirm:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={deleteConfirmText}
|
||||||
|
onChange={(e) => setDeleteConfirmText(e.target.value)}
|
||||||
|
placeholder="Type team name here"
|
||||||
|
className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex space-x-3">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => {
|
||||||
|
setShowDeleteModal(false);
|
||||||
|
setDeleteConfirmText('');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleDeleteTeam}
|
||||||
|
className="flex-1 bg-red-600 hover:bg-red-700"
|
||||||
|
disabled={isDeleting || deleteConfirmText !== team?.name}
|
||||||
|
>
|
||||||
|
{isDeleting ? 'Deleting...' : 'Delete Team'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -504,6 +504,27 @@ export const useLeaveTeam = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Delete Team Hook (Leader only)
|
||||||
|
export const useDeleteTeam = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { session } = useAuthStore();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (teamId: string) => {
|
||||||
|
if (!session?.user?.id) {
|
||||||
|
throw new Error('You must be logged in to delete a team');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hackathonApi.delete(`/teams/${teamId}`);
|
||||||
|
return { success: true };
|
||||||
|
},
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: teamKeys.myTeams() });
|
||||||
|
queryClient.invalidateQueries({ queryKey: teamKeys.lists() });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Get Teams by User ID - uses /users/{user_id}/teams
|
// Get Teams by User ID - uses /users/{user_id}/teams
|
||||||
export const useTeamsByUserId = (userId: string) => {
|
export const useTeamsByUserId = (userId: string) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
|
|||||||
Reference in New Issue
Block a user