diff --git a/src/components/moderation/ProfileManager.tsx b/src/components/moderation/ProfileManager.tsx index b4e89acc..fe2e5ab6 100644 --- a/src/components/moderation/ProfileManager.tsx +++ b/src/components/moderation/ProfileManager.tsx @@ -107,7 +107,12 @@ export function ProfileManager() { if (logError) logger.error('Failed to log admin action', { error: getErrorMessage(logError) }); - handleSuccess('Success', `User ${ban ? 'banned' : 'unbanned'} successfully.`); + handleSuccess( + 'Success', + ban + ? 'User banned successfully. They have been signed out and cannot access the application.' + : 'User unbanned successfully. They can now access the application normally.' + ); // Refresh profiles fetchProfiles(); diff --git a/src/hooks/useBanCheck.ts b/src/hooks/useBanCheck.ts index 7cc90887..81457437 100644 --- a/src/hooks/useBanCheck.ts +++ b/src/hooks/useBanCheck.ts @@ -46,7 +46,7 @@ export function useBanCheck() { checkBan(); - // Subscribe to profile changes (real-time ban detection) + // Subscribe to profile changes (real-time ban/unban detection) const channel = supabase .channel('ban-check') .on( @@ -58,7 +58,10 @@ export function useBanCheck() { filter: `user_id=eq.${user.id}` }, (payload) => { - if (payload.new && (payload.new as { banned: boolean }).banned) { + const newProfile = payload.new as { banned: boolean }; + + // Handle BAN event + if (newProfile.banned && !isBanned) { setIsBanned(true); toast({ title: 'Account Suspended', @@ -69,6 +72,17 @@ export function useBanCheck() { supabase.auth.signOut(); navigate('/'); } + + // Handle UNBAN event + if (!newProfile.banned && isBanned) { + setIsBanned(false); + toast({ + title: 'Account Restored', + description: 'Your account has been unbanned. You can now use the application normally.', + variant: 'default', + duration: 8000 + }); + } } ) .subscribe(); diff --git a/supabase/functions/_shared/banCheck.ts b/supabase/functions/_shared/banCheck.ts index e707b63d..b066f981 100644 --- a/supabase/functions/_shared/banCheck.ts +++ b/supabase/functions/_shared/banCheck.ts @@ -45,3 +45,21 @@ export function createBannedResponse(requestId: string, corsHeaders: Record) { + return new Response( + JSON.stringify({ + success: true, + message: 'Account restored. You can now access the application.', + requestId + }), + { + status: 200, + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': requestId + } + } + ); +} diff --git a/supabase/migrations/20251030015243_5e033e34-37ab-41cd-8f41-8a405c34552a.sql b/supabase/migrations/20251030015243_5e033e34-37ab-41cd-8f41-8a405c34552a.sql new file mode 100644 index 00000000..e94a2945 --- /dev/null +++ b/supabase/migrations/20251030015243_5e033e34-37ab-41cd-8f41-8a405c34552a.sql @@ -0,0 +1,37 @@ +-- Enhance invalidate_banned_user_sessions trigger to handle both ban and unban events +CREATE OR REPLACE FUNCTION public.invalidate_banned_user_sessions() +RETURNS TRIGGER +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + -- If user was just banned (banned changed from false to true) + IF NEW.banned = true AND OLD.banned = false THEN + PERFORM pg_notify( + 'user_banned', + json_build_object( + 'user_id', NEW.user_id, + 'username', NEW.username, + 'banned_at', NOW(), + 'action', 'banned' + )::text + ); + END IF; + + -- If user was just unbanned (banned changed from true to false) + IF NEW.banned = false AND OLD.banned = true THEN + PERFORM pg_notify( + 'user_unbanned', + json_build_object( + 'user_id', NEW.user_id, + 'username', NEW.username, + 'unbanned_at', NOW(), + 'action', 'unbanned' + )::text + ); + END IF; + + RETURN NEW; +END; +$$; \ No newline at end of file