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:
@@ -3,8 +3,10 @@
|
|||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import {
|
import {
|
||||||
useConsentCheckQuery,
|
useConsentCheckQuery,
|
||||||
|
useDeleteAccountMutation,
|
||||||
useGrantConsentMutation,
|
useGrantConsentMutation,
|
||||||
} from "@/services/consent"
|
} from "@/services/consent"
|
||||||
|
import { signOut } from "next-auth/react"
|
||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import { CheckCircle, Shield } from "lucide-react"
|
import { CheckCircle, Shield } from "lucide-react"
|
||||||
|
|
||||||
@@ -19,6 +21,7 @@ export function ConsentBanner() {
|
|||||||
|
|
||||||
const { data: consentCheck, isLoading } = useConsentCheckQuery()
|
const { data: consentCheck, isLoading } = useConsentCheckQuery()
|
||||||
const grantMutation = useGrantConsentMutation()
|
const grantMutation = useGrantConsentMutation()
|
||||||
|
const deleteMutation = useDeleteAccountMutation()
|
||||||
|
|
||||||
// Don't show if still loading or consent already granted
|
// Don't show if still loading or consent already granted
|
||||||
if (isLoading || consentCheck?.hasDataProcessingConsent) {
|
if (isLoading || consentCheck?.hasDataProcessingConsent) {
|
||||||
@@ -34,53 +37,60 @@ export function ConsentBanner() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReject = () => {
|
const handleReject = async () => {
|
||||||
// Redirect to deletion confirmation
|
// Delete account and sign out — dismisses the dialog by leaving the app
|
||||||
window.location.href = "/settings/privacy?action=delete"
|
await deleteMutation.mutateAsync()
|
||||||
|
await signOut({ callbackUrl: "/login" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isPending = grantMutation.isPending || deleteMutation.isPending
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm">
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-black/70 backdrop-blur-sm p-4">
|
||||||
<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="flex max-h-[min(90vh,600px)] w-full max-w-lg flex-col rounded-xl bg-card shadow-2xl">
|
||||||
<div className="mb-4 flex items-center gap-3">
|
{/* 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" />
|
<Shield className="h-8 w-8 text-primary" />
|
||||||
<h2 className="text-xl font-semibold">{t("title")}</h2>
|
<h2 className="text-xl font-semibold">{t("title")}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Required: Data Processing */}
|
{/* Scrollable content */}
|
||||||
<div className="mb-4 rounded-lg border border-primary/20 bg-primary/5 p-4">
|
<div className="flex-1 overflow-y-auto px-6 py-4">
|
||||||
<h3 className="mb-1 font-medium">{t("dataProcessing")}</h3>
|
{/* Required: Data Processing */}
|
||||||
<p className="text-sm text-muted-foreground">
|
<div className="mb-4 rounded-lg border border-primary/20 bg-primary/5 p-4">
|
||||||
{t("dataProcessingDesc")}
|
<h3 className="mb-1 font-medium">{t("dataProcessing")}</h3>
|
||||||
</p>
|
<p className="text-sm text-muted-foreground">
|
||||||
<p className="mt-2 text-xs font-medium text-primary">
|
{t("dataProcessingDesc")}
|
||||||
{t("required")}
|
</p>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
{/* Optional: Marketing */}
|
{/* Actions — fixed at bottom */}
|
||||||
<div className="mb-6 rounded-lg border p-4">
|
<div className="flex shrink-0 flex-col gap-3 border-t px-6 pt-4 pb-6">
|
||||||
<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">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleAccept}
|
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"
|
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" />
|
<CheckCircle className="h-4 w-4" />
|
||||||
@@ -89,9 +99,10 @@ export function ConsentBanner() {
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={handleReject}
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user