From e9542bc1a562a8f95aad7597c6f603286aada341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Mon, 20 Jul 2026 15:50:50 +0200 Subject: [PATCH] Harden subscribe resume and sign-out paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address PR review: avoid reintroducing cleared URL markers, treat already-closed sessions as successful logout, and stop stale subscribe/sign-out completions from racing the UI. Signed-off-by: Émile Ré --- .../SubscribeDialog/SubscribeDialog.tsx | 51 ++++++++++++++++--- .../src/lib/auth/useSignOut.ts | 8 ++- .../mailingList/SubscribeDialogProvider.tsx | 16 ++++-- pkg/server/api/trust/v1/auth_resolvers.go | 13 ++--- 4 files changed, 68 insertions(+), 20 deletions(-) diff --git a/apps/compliance-portal/src/components/SubscribeDialog/SubscribeDialog.tsx b/apps/compliance-portal/src/components/SubscribeDialog/SubscribeDialog.tsx index 5969dd867..da4d02ff3 100644 --- a/apps/compliance-portal/src/components/SubscribeDialog/SubscribeDialog.tsx +++ b/apps/compliance-portal/src/components/SubscribeDialog/SubscribeDialog.tsx @@ -29,7 +29,7 @@ import { DialogTitle } from "@probo/ui/src/v2/Dialog/DialogTitle"; import { Field } from "@probo/ui/src/v2/form/Field"; import { TextField } from "@probo/ui/src/v2/form/TextField"; import { Text } from "@probo/ui/src/v2/typography/Text"; -import { type FormEvent, useState } from "react"; +import { type FormEvent, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSubscribeToMailingList } from "#/lib/mailingList/useSubscribeToMailingList"; @@ -45,7 +45,8 @@ interface SubscribeDialogProps { } // Auth-gated mailing-list subscribe confirmation. The form only mounts while -// open so each open starts clean without a reset effect. +// open so each open starts clean without a reset effect. Dismiss is blocked +// while the mutation is in flight so a Cancel/Escape cannot race a reopen. export function SubscribeDialog({ open, onOpenChange, @@ -53,12 +54,23 @@ export function SubscribeDialog({ viewerEmail, organizationName, }: SubscribeDialogProps) { + const [isSubmitting, setIsSubmitting] = useState(false); + return ( - + { + if (!next && isSubmitting) { + return; + } + onOpenChange(next); + }} + > {open && ( onOpenChange(false)} + onSubmittingChange={setIsSubmitting} trustCenterId={trustCenterId} viewerEmail={viewerEmail} organizationName={organizationName} @@ -71,6 +83,7 @@ export function SubscribeDialog({ interface SubscribeFormProps { onClose: () => void; + onSubmittingChange: (submitting: boolean) => void; trustCenterId: string; viewerEmail: string; organizationName: string; @@ -78,23 +91,39 @@ interface SubscribeFormProps { function SubscribeForm({ onClose, + onSubmittingChange, trustCenterId, viewerEmail, organizationName, }: SubscribeFormProps) { const { t } = useTranslation("updates"); const [subscribe, isSubscribing] = useSubscribeToMailingList(trustCenterId); - const [submitted, setSubmitted] = useState(false); + const aliveRef = useRef(true); + + useEffect(() => { + aliveRef.current = true; + return () => { + aliveRef.current = false; + }; + }, []); + + useEffect(() => { + onSubmittingChange(isSubscribing); + return () => { + onSubmittingChange(false); + }; + }, [isSubscribing, onSubmittingChange]); const onSubmit = async (event: FormEvent) => { event.preventDefault(); - if (submitted) { + if (isSubscribing) { return; } try { await subscribe(); - setSubmitted(true); - onClose(); + if (aliveRef.current) { + onClose(); + } } catch { // Errors are surfaced by the mutation notifier; keep the form open. } @@ -119,7 +148,13 @@ function SubscribeForm({ -