From b491e832a47a8855f4c7e7ee0d1a7560756ab0e3 Mon Sep 17 00:00:00 2001 From: Developing-Gamer Date: Sat, 4 Apr 2026 21:45:40 -0400 Subject: [PATCH 1/2] Add Neon project transfer confirmation page and refactor transfer confirmation components - Introduced a new `neon-transfer-confirm-page.tsx` for handling project transfer confirmations specific to Neon, maintaining legacy UI and behavior. - Refactored existing `transfer-confirm-page.tsx` to support a new `TransferConfirmMissingCodeView` for handling cases where the transfer code is missing. - Updated integration pages for both Neon and custom transfers to utilize the new components, enhancing code organization and reusability. - Added a new `project-transfer-confirm-view.tsx` component to standardize the UI for project transfer confirmations across different integrations. These changes improve the user experience during project transfers and streamline the integration process for different services. --- .../custom/projects/transfer/confirm/page.tsx | 8 +- .../neon-transfer-confirm-page.tsx | 151 ++++++++++++++++ .../neon/projects/transfer/confirm/page.tsx | 4 +- .../integrations/transfer-confirm-page.tsx | 171 ++++++------------ .../project-transfer-confirm-view.tsx | 132 ++++++++++++++ 5 files changed, 348 insertions(+), 118 deletions(-) create mode 100644 apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx create mode 100644 apps/dashboard/src/components/project-transfer-confirm-view.tsx diff --git a/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx b/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx index f8b73e9a78..1202b2d260 100644 --- a/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx @@ -1,4 +1,4 @@ -import IntegrationProjectTransferConfirmPageClient from "@/app/(main)/integrations/transfer-confirm-page"; +import IntegrationProjectTransferConfirmPageClient, { TransferConfirmMissingCodeView } from "@/app/(main)/integrations/transfer-confirm-page"; export const metadata = { title: "Project transfer", @@ -7,14 +7,12 @@ export const metadata = { export default async function Page(props: { searchParams: Promise<{ code?: string }> }) { const transferCode = (await props.searchParams).code; if (!transferCode) { - return <> -
Error: No transfer code provided.
- ; + return ; } return ( <> - + ); } diff --git a/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx b/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx new file mode 100644 index 0000000000..79461420aa --- /dev/null +++ b/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { Logo } from "@/components/logo"; +import { useRouter } from "@/components/router"; +import { Button, Card, CardContent, CardFooter, CardHeader, Input, Typography } from "@/components/ui"; +import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; +import { useStackApp, useUser } from "@stackframe/stack"; +import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises"; +import Image from "next/image"; +import { useSearchParams } from "next/navigation"; +import { useEffect, useState } from "react"; +import NeonLogo from "../../../../public/neon.png"; + +type NeonTransferState = "loading" | "success" | { type: "error", message: string }; + +/** + * Neon project transfer confirmation — legacy UI and copy (unchanged from pre–custom-redesign behavior). + */ +export default function NeonIntegrationProjectTransferConfirmPageClient() { + const app = useStackApp(); + const user = useUser({ projectIdMustMatch: "internal" }); + const router = useRouter(); + const searchParams = useSearchParams(); + + const [state, setState] = useState("loading"); + + useEffect(() => { + runAsynchronously(async () => { + try { + await (app as any)[stackAppInternalsSymbol].sendRequest("/integrations/neon/projects/transfer/confirm/check", { + method: "POST", + body: JSON.stringify({ + code: searchParams.get("code"), + }), + headers: { + "Content-Type": "application/json", + }, + }); + setState("success"); + } catch (err: any) { + setState({ type: "error", message: err.message }); + } + }); + }, [app, searchParams]); + + const currentUrl = new URL(window.location.href); + const signUpSearchParams = new URLSearchParams(); + signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); + const signUpUrl = `/handler/signup?${signUpSearchParams.toString()}`; + + return ( + + + Neon +
+
+
+
+
+
+
+
+ + + +

+ Project transfer +

+ {state === "success" && <> + + Neon would like to transfer a Stack Auth project and link it to your own account. This will let you access the project from Stack Auth's dashboard. + + {user ? ( + <> + + Which Stack Auth account would you like to transfer the project to? (You'll still be able to access your project from Neon's dashboard.) + + } value={`Signed in as ${user.primaryEmail || user.displayName || "Unnamed user"}`} /> + + + ) : ( + + To continue, please sign in or create a Stack Auth account. + + )} + } + + {typeof state !== "string" && <> + + {state.message} + + } + +
+ {state === "success" && +
+ + +
+
} + + ); +} diff --git a/apps/dashboard/src/app/(main)/integrations/neon/projects/transfer/confirm/page.tsx b/apps/dashboard/src/app/(main)/integrations/neon/projects/transfer/confirm/page.tsx index f4cae6b4d2..27401cbf02 100644 --- a/apps/dashboard/src/app/(main)/integrations/neon/projects/transfer/confirm/page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/neon/projects/transfer/confirm/page.tsx @@ -1,4 +1,4 @@ -import IntegrationProjectTransferConfirmPageClient from "@/app/(main)/integrations/transfer-confirm-page"; +import NeonIntegrationProjectTransferConfirmPageClient from "@/app/(main)/integrations/neon-transfer-confirm-page"; export const metadata = { title: "Project transfer", @@ -14,7 +14,7 @@ export default async function Page(props: { searchParams: Promise<{ code?: strin return ( <> - + ); } diff --git a/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx b/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx index 6b19aa3dd7..bd0e0da2ee 100644 --- a/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx @@ -1,28 +1,40 @@ "use client"; -import { Logo } from "@/components/logo"; +import { DesignAlert } from "@/components/design-components/alert"; +import { ProjectTransferConfirmView, type ProjectTransferConfirmUiState } from "@/components/project-transfer-confirm-view"; import { useRouter } from "@/components/router"; -import { Button, Card, CardContent, CardFooter, CardHeader, Input, Typography } from "@/components/ui"; import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; import { useStackApp, useUser } from "@stackframe/stack"; import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises"; -import Image from "next/image"; import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; -import NeonLogo from "../../../../public/neon.png"; -export default function IntegrationProjectTransferConfirmPageClient(props: { type: "neon" | "custom" }) { +export function TransferConfirmMissingCodeView() { + return ( +
+ +
+ ); +} + +/** Custom integration project transfer — design-components UI. Neon uses `neon-transfer-confirm-page`. */ +export default function IntegrationProjectTransferConfirmPageClient() { const app = useStackApp(); const user = useUser({ projectIdMustMatch: "internal" }); const router = useRouter(); const searchParams = useSearchParams(); - const [state, setState] = useState<'loading'|'success'|{type: 'error', message: string}>('loading'); + const [state, setState] = useState("loading"); useEffect(() => { runAsynchronously(async () => { try { - await (app as any)[stackAppInternalsSymbol].sendRequest(`/integrations/${props.type}/projects/transfer/confirm/check`, { + await (app as any)[stackAppInternalsSymbol].sendRequest("/integrations/custom/projects/transfer/confirm/check", { method: "POST", body: JSON.stringify({ code: searchParams.get("code"), @@ -31,119 +43,56 @@ export default function IntegrationProjectTransferConfirmPageClient(props: { typ "Content-Type": "application/json", }, }); - setState('success'); + setState("success"); } catch (err: any) { - setState({ type: 'error', message: err.message }); + setState({ type: "error", message: err.message }); } }); - - }, [app, searchParams, props.type]); + }, [app, searchParams]); const currentUrl = new URL(window.location.href); const signUpSearchParams = new URLSearchParams(); signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); const signUpUrl = `/handler/signup?${signUpSearchParams.toString()}`; - return ( - - - {props.type === "neon" && (<> - Neon -
-
-
-
-
-
-
-
- )} - - - -

- Project transfer -

- {state === 'success' && <> - - {props.type === "neon" ? "Neon" : "A third party"} would like to transfer a Stack Auth project and link it to your own account. This will let you access the project from Stack Auth's dashboard. - - {user ? ( - <> - - Which Stack Auth account would you like to transfer the project to? (You'll still be able to access your project from {props.type === "neon" ? "Neon" : "the third party"}'s dashboard.) - - } value={`Signed in as ${user.primaryEmail || user.displayName || "Unnamed user"}`} /> - - - ) : ( - - To continue, please sign in or create a Stack Auth account. - - )} - } - - {typeof state !== 'string' && <> - - {state.message} - - } + const signedIn = user != null; + const accountLabel = user + ? `Signed in as ${user.primaryEmail ?? user.displayName ?? "Unnamed user"}` + : undefined; -
- {state === 'success' && -
- - -
-
} - + return ( + { + window.close(); + }} + onPrimary={async () => { + if (user) { + const confirmRes = await (app as any)[stackAppInternalsSymbol].sendRequest("/integrations/custom/projects/transfer/confirm", { + method: "POST", + body: JSON.stringify({ + code: searchParams.get("code"), + }), + headers: { + "Content-Type": "application/json", + }, + }); + const confirmResJson = await confirmRes.json(); + router.push(`/projects/${confirmResJson.project_id}`); + await wait(3000); + } else { + router.push(signUpUrl); + await wait(3000); + } + }} + onSwitchAccount={async () => { + if (user == null) { + return; + } + await user.signOut({ redirectUrl: signUpUrl }); + }} + /> ); } diff --git a/apps/dashboard/src/components/project-transfer-confirm-view.tsx b/apps/dashboard/src/components/project-transfer-confirm-view.tsx new file mode 100644 index 0000000000..7de5e391c7 --- /dev/null +++ b/apps/dashboard/src/components/project-transfer-confirm-view.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { DesignAlert } from "@/components/design-components/alert"; +import { DesignButton } from "@/components/design-components/button"; +import { DesignCard } from "@/components/design-components/card"; +import { DesignInput } from "@/components/design-components/input"; +import { Logo } from "@/components/logo"; +import { Spinner } from "@/components/ui"; +import { ArrowsLeftRightIcon } from "@phosphor-icons/react"; + +export type ProjectTransferConfirmUiState = "loading" | "success" | { type: "error", message: string }; + +export type ProjectTransferConfirmViewProps = { + state: ProjectTransferConfirmUiState, + /** When `state === "success"`, whether the “signed in” branch is shown. */ + signedIn: boolean, + /** Label for the disabled “Receiving account” field when signed in. */ + signedInAsLabel?: string, + onCancel?: () => void | Promise, + onPrimary?: () => void | Promise, + onSwitchAccount?: () => void | Promise, +}; + +/** Presentational shell for the custom integration project transfer confirmation screen. */ +export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProps) { + const { + state, + signedIn, + signedInAsLabel = "Signed in as preview@example.com", + onCancel, + onPrimary, + onSwitchAccount, + } = props; + + const primaryLabel = signedIn ? "Accept transfer" : "Sign in"; + + return ( +
+ + +
+ )} + > + {state === "loading" && ( +
+ +

Verifying this transfer link…

+
+ )} + + {state === "success" && ( +
+ {signedIn ? ( + <> +

+ You'll still be able to open this project from the third party's dashboard after you accept. +

+
+ + Receiving account + + } + value={signedInAsLabel} + /> +
+ { + await onSwitchAccount?.(); + }} + > + Use a different account + + + ) : ( + + )} +
+ )} + + {typeof state !== "string" && ( + + )} + + {state === "success" && ( +
+ { + await onCancel?.(); + }} + > + Cancel + + { + await onPrimary?.(); + }} + > + {primaryLabel} + +
+ )} + +
+ ); +} From cd28f101c5b6be50e132612b4a8d66ea32f3bffb Mon Sep 17 00:00:00 2001 From: Aadesh Kheria Date: Wed, 13 May 2026 15:09:39 -0700 Subject: [PATCH 2/2] bot comments --- .../widget-playground/page-client.tsx | 3 +- .../custom/projects/transfer/confirm/page.tsx | 38 +++++++-- .../neon-transfer-confirm-page.tsx | 77 ++++++++++++------- .../integrations/transfer-confirm-page.tsx | 40 +++++----- .../project-transfer-confirm-view.tsx | 37 ++++++--- 5 files changed, 126 insertions(+), 69 deletions(-) diff --git a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx index bbedefbd7d..a457aa04bb 100644 --- a/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx +++ b/apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/widget-playground/page-client.tsx @@ -1631,7 +1631,8 @@ function Draggable(props: { A runtime error occured while rendering this widget.



{errorToNiceString(props.error)} diff --git a/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx b/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx index 1202b2d260..9fdf9bdc1a 100644 --- a/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/custom/projects/transfer/confirm/page.tsx @@ -1,18 +1,42 @@ -import IntegrationProjectTransferConfirmPageClient, { TransferConfirmMissingCodeView } from "@/app/(main)/integrations/transfer-confirm-page"; +import CustomIntegrationProjectTransferConfirmPageClient from "@/app/(main)/integrations/transfer-confirm-page"; export const metadata = { title: "Project transfer", }; +function MissingCodeView() { + return ( +
+
+ +
+
+ This transfer link is incomplete +
+

+ Open the full link you received (it includes a transfer code). If the link expired, go back to the partner or integrations screen and start the transfer again. +

+
+
+
+ ); +} + export default async function Page(props: { searchParams: Promise<{ code?: string }> }) { const transferCode = (await props.searchParams).code; if (!transferCode) { - return ; + return ; } - return ( - <> - - - ); + return ; } diff --git a/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx b/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx index 79461420aa..3795b6bf4b 100644 --- a/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/neon-transfer-confirm-page.tsx @@ -5,7 +5,8 @@ import { useRouter } from "@/components/router"; import { Button, Card, CardContent, CardFooter, CardHeader, Input, Typography } from "@/components/ui"; import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; import { useStackApp, useUser } from "@stackframe/stack"; -import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises"; +import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; +import { runAsynchronously, runAsynchronouslyWithAlert, wait } from "@stackframe/stack-shared/dist/utils/promises"; import Image from "next/image"; import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; @@ -13,6 +14,13 @@ import NeonLogo from "../../../../public/neon.png"; type NeonTransferState = "loading" | "success" | { type: "error", message: string }; +function buildSignUpUrl(): string { + const currentUrl = new URL(window.location.href); + const signUpSearchParams = new URLSearchParams(); + signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); + return `/handler/signup?${signUpSearchParams.toString()}`; +} + /** * Neon project transfer confirmation — legacy UI and copy (unchanged from pre–custom-redesign behavior). */ @@ -37,17 +45,16 @@ export default function NeonIntegrationProjectTransferConfirmPageClient() { }, }); setState("success"); - } catch (err: any) { - setState({ type: "error", message: err.message }); + } catch (err: unknown) { + console.error("Neon project transfer confirm check failed:", err); + setState({ + type: "error", + message: "This transfer link is invalid, has expired, or has already been used. Return to your Neon dashboard and start the transfer again.", + }); } }); }, [app, searchParams]); - const currentUrl = new URL(window.location.href); - const signUpSearchParams = new URLSearchParams(); - signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); - const signUpUrl = `/handler/signup?${signUpSearchParams.toString()}`; - return ( @@ -92,15 +99,22 @@ export default function NeonIntegrationProjectTransferConfirmPageClient() { {state === "success" && <> - Neon would like to transfer a Stack Auth project and link it to your own account. This will let you access the project from Stack Auth's dashboard. + {"Neon would like to transfer a Stack Auth project and link it to your own account. This will let you access the project from Stack Auth's dashboard."} {user ? ( <> - Which Stack Auth account would you like to transfer the project to? (You'll still be able to access your project from Neon's dashboard.) + {"Which Stack Auth account would you like to transfer the project to? (You'll still be able to access your project from Neon's dashboard.)"} } value={`Signed in as ${user.primaryEmail || user.displayName || "Unnamed user"}`} /> - @@ -123,24 +137,29 @@ export default function NeonIntegrationProjectTransferConfirmPageClient() { - diff --git a/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx b/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx index bd0e0da2ee..c86e0f5cdf 100644 --- a/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx +++ b/apps/dashboard/src/app/(main)/integrations/transfer-confirm-page.tsx @@ -1,29 +1,23 @@ "use client"; -import { DesignAlert } from "@/components/design-components/alert"; import { ProjectTransferConfirmView, type ProjectTransferConfirmUiState } from "@/components/project-transfer-confirm-view"; import { useRouter } from "@/components/router"; import { stackAppInternalsSymbol } from "@/lib/stack-app-internals"; import { useStackApp, useUser } from "@stackframe/stack"; +import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; import { runAsynchronously, wait } from "@stackframe/stack-shared/dist/utils/promises"; import { useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; -export function TransferConfirmMissingCodeView() { - return ( -
- -
- ); +function buildSignUpUrl(): string { + const currentUrl = new URL(window.location.href); + const signUpSearchParams = new URLSearchParams(); + signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); + return `/handler/signup?${signUpSearchParams.toString()}`; } /** Custom integration project transfer — design-components UI. Neon uses `neon-transfer-confirm-page`. */ -export default function IntegrationProjectTransferConfirmPageClient() { +export default function CustomIntegrationProjectTransferConfirmPageClient() { const app = useStackApp(); const user = useUser({ projectIdMustMatch: "internal" }); const router = useRouter(); @@ -44,17 +38,16 @@ export default function IntegrationProjectTransferConfirmPageClient() { }, }); setState("success"); - } catch (err: any) { - setState({ type: "error", message: err.message }); + } catch (err: unknown) { + console.error("Project transfer confirm check failed:", err); + setState({ + type: "error", + message: "This transfer link is invalid, has expired, or has already been used. Open the original link from the partner or integrations dashboard, or start the transfer again.", + }); } }); }, [app, searchParams]); - const currentUrl = new URL(window.location.href); - const signUpSearchParams = new URLSearchParams(); - signUpSearchParams.set("after_auth_return_to", currentUrl.pathname + currentUrl.search + currentUrl.hash); - const signUpUrl = `/handler/signup?${signUpSearchParams.toString()}`; - const signedIn = user != null; const accountLabel = user ? `Signed in as ${user.primaryEmail ?? user.displayName ?? "Unnamed user"}` @@ -80,10 +73,13 @@ export default function IntegrationProjectTransferConfirmPageClient() { }, }); const confirmResJson = await confirmRes.json(); + if (typeof confirmResJson?.project_id !== "string") { + throw new StackAssertionError("Project transfer confirm response is missing `project_id`", { confirmResJson }); + } router.push(`/projects/${confirmResJson.project_id}`); await wait(3000); } else { - router.push(signUpUrl); + router.push(buildSignUpUrl()); await wait(3000); } }} @@ -91,7 +87,7 @@ export default function IntegrationProjectTransferConfirmPageClient() { if (user == null) { return; } - await user.signOut({ redirectUrl: signUpUrl }); + await user.signOut({ redirectUrl: buildSignUpUrl() }); }} /> ); diff --git a/apps/dashboard/src/components/project-transfer-confirm-view.tsx b/apps/dashboard/src/components/project-transfer-confirm-view.tsx index 7de5e391c7..024b090c6f 100644 --- a/apps/dashboard/src/components/project-transfer-confirm-view.tsx +++ b/apps/dashboard/src/components/project-transfer-confirm-view.tsx @@ -7,6 +7,8 @@ import { DesignInput } from "@/components/design-components/input"; import { Logo } from "@/components/logo"; import { Spinner } from "@/components/ui"; import { ArrowsLeftRightIcon } from "@phosphor-icons/react"; +import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; +import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises"; export type ProjectTransferConfirmUiState = "loading" | "success" | { type: "error", message: string }; @@ -14,7 +16,7 @@ export type ProjectTransferConfirmViewProps = { state: ProjectTransferConfirmUiState, /** When `state === "success"`, whether the “signed in” branch is shown. */ signedIn: boolean, - /** Label for the disabled “Receiving account” field when signed in. */ + /** Label for the disabled “Receiving account” field. Required when `state === "success"` and `signedIn === true`. */ signedInAsLabel?: string, onCancel?: () => void | Promise, onPrimary?: () => void | Promise, @@ -26,12 +28,21 @@ export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProp const { state, signedIn, - signedInAsLabel = "Signed in as preview@example.com", + signedInAsLabel, onCancel, onPrimary, onSwitchAccount, } = props; + if (state === "success") { + if (onCancel == null || onPrimary == null) { + throw new StackAssertionError("ProjectTransferConfirmView requires `onCancel` and `onPrimary` in the success state"); + } + if (signedIn && (signedInAsLabel == null || onSwitchAccount == null)) { + throw new StackAssertionError("ProjectTransferConfirmView requires `signedInAsLabel` and `onSwitchAccount` when `signedIn` is true in the success state"); + } + } + const primaryLabel = signedIn ? "Accept transfer" : "Sign in"; return ( @@ -61,7 +72,7 @@ export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProp {signedIn ? ( <>

- You'll still be able to open this project from the third party's dashboard after you accept. + {"You'll still be able to open this project from the third party's dashboard after you accept."}

@@ -78,8 +89,10 @@ export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProp { - await onSwitchAccount?.(); + onClick={() => { + runAsynchronouslyWithAlert(async () => { + await onSwitchAccount?.(); + }); }} > Use a different account @@ -89,7 +102,7 @@ export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProp )} @@ -110,16 +123,20 @@ export function ProjectTransferConfirmView(props: ProjectTransferConfirmViewProp { - await onCancel?.(); + onClick={() => { + runAsynchronouslyWithAlert(async () => { + await onCancel?.(); + }); }} > Cancel { - await onPrimary?.(); + onClick={() => { + runAsynchronouslyWithAlert(async () => { + await onPrimary?.(); + }); }} > {primaryLabel}