Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1631,7 +1631,8 @@ function Draggable(props: {
A runtime error occured while rendering this widget.<br />
<br />
<button className="text-blue-500 hover:underline" onClick={() => {
props.reset();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- tsc + eslint disagree on whether `reset` is nullable here (Next 16.2 type drift).
props.reset?.();
}}>Reload widget</button><br />
<br />
{errorToNiceString(props.error)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import IntegrationProjectTransferConfirmPageClient 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 (
<div className="flex min-h-[100dvh] w-full items-center justify-center px-4 py-8 sm:px-6">
<div
role="alert"
className="relative flex w-full max-w-md items-start gap-3 rounded-2xl border border-red-500/30 bg-red-500/[0.06] p-4 text-sm"
>
<svg
aria-hidden="true"
viewBox="0 0 256 256"
className="mt-0.5 h-4 w-4 flex-shrink-0 text-red-500"
fill="currentColor"
>
<path d="M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm37.66,130.34a8,8,0,0,1-11.32,11.32L128,139.31l-26.34,26.35a8,8,0,0,1-11.32-11.32L116.69,128,90.34,101.66a8,8,0,0,1,11.32-11.32L128,116.69l26.34-26.35a8,8,0,0,1,11.32,11.32L139.31,128Z" />
</svg>
<div className="min-w-0">
<h5 className="mb-1 font-medium leading-none tracking-tight text-red-600 dark:text-red-400">
This transfer link is incomplete
</h5>
<p className="text-sm leading-relaxed text-foreground/80 dark:text-muted-foreground">
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.
</p>
</div>
</div>
</div>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Page component uses await on props.searchParams making the route dynamic when it should remain static

Fix on Vercel

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Page component uses await on props.searchParams making the route dynamic when it should remain static

Fix on Vercel

export default async function Page(props: { searchParams: Promise<{ code?: string }> }) {
const transferCode = (await props.searchParams).code;
if (!transferCode) {
return <>
<div>Error: No transfer code provided.</div>
</>;
return <MissingCodeView />;
}
Comment thread
aadesh18 marked this conversation as resolved.

return (
<>
<IntegrationProjectTransferConfirmPageClient type="custom" />
</>
);
return <CustomIntegrationProjectTransferConfirmPageClient />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
"use client";

import { Logo } from "@/components/logo";
import { useRouter } from "@/components/router";
import { Button, Card, CardContent, CardFooter, CardHeader, Input, Typography } from "@/components/ui";
import { buildTransferSignUpUrl, getStackAppInternals } from "@/lib/transfer-utils";
import { useStackApp, useUser } from "@stackframe/stack";
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";
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<NeonTransferState>("loading");

useEffect(() => {
runAsynchronously(async () => {
try {
await getStackAppInternals(app).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: 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]);

return (
<Card className="max-w-lg text-center">
<CardHeader className="flex-row items-end justify-center gap-4">
<Image src={NeonLogo} alt="Neon" width={55} />
<div className="relative self-center w-10 hidden dark:block">
<div style={{
position: "absolute",
width: 40,
height: 6,
backgroundImage: "repeating-linear-gradient(135deg, #ccc, #ccc)",
transform: "rotate(-45deg)",
}} />
<div style={{
position: "absolute",
width: 40,
height: 6,
backgroundImage: "repeating-linear-gradient(45deg, #ccc, #ccc)",
transform: "rotate(45deg)",
}} />
</div>
<div className="relative self-center w-10 block dark:hidden">
<div style={{
position: "absolute",
width: 40,
height: 6,
backgroundImage: "repeating-linear-gradient(135deg, #52525B, #52525B)",
transform: "rotate(-45deg)",
}} />
<div style={{
position: "absolute",
width: 40,
height: 6,
backgroundImage: "repeating-linear-gradient(45deg, #52525B, #52525B)",
transform: "rotate(45deg)",
}} />
</div>
<Logo noLink alt="Stack" width={50} />
</CardHeader>
<CardContent className="flex flex-col gap-4">
<h1 className="text-3xl font-semibold">
Project transfer
</h1>
{state === "success" && <>
<Typography className="text-sm">
{"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."}
</Typography>
{user ? (
<>
<Typography className="mb-3 text-sm">
{"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.)"}
</Typography>
<Input type="text" disabled prefixItem={<Logo noLink width={15} height={15} />} value={`Signed in as ${user.primaryEmail || user.displayName || "Unnamed user"}`} />
<Button
variant="secondary"
onClick={() => {
runAsynchronouslyWithAlert(async () => {
await user.signOut({ redirectUrl: buildTransferSignUpUrl() });
});
}}
>
Switch account
</Button>
</>
) : (
<Typography className="text-sm">
To continue, please sign in or create a Stack Auth account.
</Typography>
)}
</>}

{typeof state !== "string" && <>
<Typography className="text-sm">
{state.message}
</Typography>
</>}

</CardContent>
{state === "success" && <CardFooter className="flex justify-end mt-4">
<div className="flex gap-2 justify-center">
<Button variant="secondary" onClick={() => { window.close(); }}>
Cancel
</Button>
<Button onClick={() => {
runAsynchronouslyWithAlert(async () => {
if (user) {
const confirmRes = await getStackAppInternals(app).sendRequest("/integrations/neon/projects/transfer/confirm", {
method: "POST",
body: JSON.stringify({
code: searchParams.get("code"),
}),
headers: {
"Content-Type": "application/json",
},
});
const confirmResJson = await confirmRes.json();
if (typeof confirmResJson?.project_id !== "string") {
throw new StackAssertionError("Neon project transfer confirm response is missing `project_id`", { confirmResJson });
}
router.push(`/projects/${confirmResJson.project_id}`);
await wait(3000);
} else {
router.push(buildTransferSignUpUrl());
await wait(3000);
}
});
}}>
{user ? "Transfer" : "Sign in"}
</Button>
</div>
</CardFooter>}
</Card>
);
}
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -14,7 +14,7 @@ export default async function Page(props: { searchParams: Promise<{ code?: strin

return (
<>
<IntegrationProjectTransferConfirmPageClient type="neon" />
<NeonIntegrationProjectTransferConfirmPageClient />
</>
);
}
Loading