fix: consent banner dismiss on decline + short viewport layout

Bug 1: Clicking 'Ablehnen' now properly dismisses the dialog by calling
the delete account mutation and signing out (previously it redirected to
/settings/privacy which re-rendered the banner in a loop).

Bug 2: Restructured the dialog layout with flex-col + overflow-y-auto on
the content area only. Header and action buttons are pinned (shrink-0)
so they're always accessible on short viewports. Added max-h constraint
with min() to cap at 600px or 90vh.
This commit is contained in:
Patrick Plate
2026-06-13 17:11:20 +02:00
parent cd77eb6448
commit 27690a836e
@@ -3,8 +3,10 @@
import { useState } from "react"
import {
useConsentCheckQuery,
useDeleteAccountMutation,
useGrantConsentMutation,
} from "@/services/consent"
import { signOut } from "next-auth/react"
import { useTranslations } from "next-intl"
import { CheckCircle, Shield } from "lucide-react"
@@ -19,6 +21,7 @@ export function ConsentBanner() {
const { data: consentCheck, isLoading } = useConsentCheckQuery()
const grantMutation = useGrantConsentMutation()
const deleteMutation = useDeleteAccountMutation()
// Don't show if still loading or consent already granted
if (isLoading || consentCheck?.hasDataProcessingConsent) {
@@ -34,53 +37,60 @@ export function ConsentBanner() {
}
}
const handleReject = () => {
// Redirect to deletion confirmation
window.location.href = "/settings/privacy?action=delete"
const handleReject = async () => {
// Delete account and sign out — dismisses the dialog by leaving the app
await deleteMutation.mutateAsync()
await signOut({ callbackUrl: "/login" })
}
const isPending = grantMutation.isPending || deleteMutation.isPending
return (
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm">
<div className="mx-4 max-h-[90vh] w-full max-w-lg overflow-y-auto rounded-xl bg-card p-6 shadow-2xl">
<div className="mb-4 flex items-center gap-3">
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
<div className="flex max-h-[min(90vh,600px)] w-full max-w-lg flex-col rounded-xl bg-card shadow-2xl">
{/* Header — fixed */}
<div className="flex shrink-0 items-center gap-3 border-b px-6 pt-6 pb-4">
<Shield className="h-8 w-8 text-primary" />
<h2 className="text-xl font-semibold">{t("title")}</h2>
</div>
{/* Required: Data Processing */}
<div className="mb-4 rounded-lg border border-primary/20 bg-primary/5 p-4">
<h3 className="mb-1 font-medium">{t("dataProcessing")}</h3>
<p className="text-sm text-muted-foreground">
{t("dataProcessingDesc")}
</p>
<p className="mt-2 text-xs font-medium text-primary">
{t("required")}
</p>
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto px-6 py-4">
{/* Required: Data Processing */}
<div className="mb-4 rounded-lg border border-primary/20 bg-primary/5 p-4">
<h3 className="mb-1 font-medium">{t("dataProcessing")}</h3>
<p className="text-sm text-muted-foreground">
{t("dataProcessingDesc")}
</p>
<p className="mt-2 text-xs font-medium text-primary">
{t("required")}
</p>
</div>
{/* Optional: Marketing */}
<div className="rounded-lg border p-4">
<label className="flex cursor-pointer items-start gap-3">
<input
type="checkbox"
checked={marketingChecked}
onChange={(e) => setMarketingChecked(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300"
/>
<div>
<h3 className="font-medium">{t("marketing")}</h3>
<p className="text-sm text-muted-foreground">
{t("marketingDesc")}
</p>
</div>
</label>
</div>
</div>
{/* Optional: Marketing */}
<div className="mb-6 rounded-lg border p-4">
<label className="flex cursor-pointer items-start gap-3">
<input
type="checkbox"
checked={marketingChecked}
onChange={(e) => setMarketingChecked(e.target.checked)}
className="mt-1 h-4 w-4 rounded border-gray-300"
/>
<div>
<h3 className="font-medium">{t("marketing")}</h3>
<p className="text-sm text-muted-foreground">
{t("marketingDesc")}
</p>
</div>
</label>
</div>
{/* Actions */}
<div className="flex flex-col gap-3">
{/* Actions — fixed at bottom */}
<div className="flex shrink-0 flex-col gap-3 border-t px-6 pt-4 pb-6">
<button
onClick={handleAccept}
disabled={grantMutation.isPending}
disabled={isPending}
className="flex w-full items-center justify-center gap-2 rounded-lg bg-primary px-4 py-3 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90 disabled:opacity-50"
>
<CheckCircle className="h-4 w-4" />
@@ -89,9 +99,10 @@ export function ConsentBanner() {
<button
onClick={handleReject}
className="w-full text-center text-sm text-destructive underline transition-colors hover:text-destructive/80"
disabled={isPending}
className="w-full text-center text-sm text-destructive underline transition-colors hover:text-destructive/80 disabled:opacity-50"
>
{t("reject")}
{deleteMutation.isPending ? "..." : t("reject")}
</button>
</div>
</div>