From 350485612ee15c7dc49dc0f62bc5e7f740c461ed Mon Sep 17 00:00:00 2001 From: Adrian Astles <49412215+adrianeastles@users.noreply.github.com> Date: Fri, 25 Jul 2025 22:46:40 +0800 Subject: [PATCH] This improves the user experience by automatically filling the email field and preventing users from changing the email they were invited with. - Update invite link generation to include email parameter in URL - Modify signup form to pre-fill and lock email field when provided via invite - Update invite page and status card to preserve email through redirect chain - Ensure existing invite URLs continue to work without breaking changes --- server/routers/user/inviteUser.ts | 4 ++-- src/app/auth/signup/SignupForm.tsx | 11 ++++++++--- src/app/auth/signup/page.tsx | 6 +++++- src/app/invite/InviteStatusCard.tsx | 12 ++++++++++-- src/app/invite/page.tsx | 13 ++++++++++--- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/server/routers/user/inviteUser.ts b/server/routers/user/inviteUser.ts index 837ef179..174600fc 100644 --- a/server/routers/user/inviteUser.ts +++ b/server/routers/user/inviteUser.ts @@ -189,7 +189,7 @@ export async function inviteUser( ) ); - const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}`; + const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; if (doEmail) { await sendEmail( @@ -241,7 +241,7 @@ export async function inviteUser( }); }); - const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}`; + const inviteLink = `${config.getRawConfig().app.dashboard_url}/invite?token=${inviteId}-${token}&email=${encodeURIComponent(email)}`; if (doEmail) { await sendEmail( diff --git a/src/app/auth/signup/SignupForm.tsx b/src/app/auth/signup/SignupForm.tsx index d6d79eb7..f4690683 100644 --- a/src/app/auth/signup/SignupForm.tsx +++ b/src/app/auth/signup/SignupForm.tsx @@ -75,6 +75,7 @@ type SignupFormProps = { redirect?: string; inviteId?: string; inviteToken?: string; + emailParam?: string; }; const formSchema = z @@ -103,7 +104,8 @@ const formSchema = z export default function SignupForm({ redirect, inviteId, - inviteToken + inviteToken, + emailParam }: SignupFormProps) { const router = useRouter(); const api = createApiClient(useEnvContext()); @@ -118,7 +120,7 @@ export default function SignupForm({ const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { - email: "", + email: emailParam || "", password: "", confirmPassword: "", agreeToTerms: false @@ -209,7 +211,10 @@ export default function SignupForm({ {t("email")} - + diff --git a/src/app/auth/signup/page.tsx b/src/app/auth/signup/page.tsx index debd7c58..673e69bf 100644 --- a/src/app/auth/signup/page.tsx +++ b/src/app/auth/signup/page.tsx @@ -11,7 +11,10 @@ import { getTranslations } from "next-intl/server"; export const dynamic = "force-dynamic"; export default async function Page(props: { - searchParams: Promise<{ redirect: string | undefined }>; + searchParams: Promise<{ + redirect: string | undefined; + email: string | undefined; + }>; }) { const searchParams = await props.searchParams; const getUser = cache(verifySession); @@ -69,6 +72,7 @@ export default async function Page(props: { redirect={redirectUrl} inviteToken={inviteToken} inviteId={inviteId} + emailParam={searchParams.email} />

diff --git a/src/app/invite/InviteStatusCard.tsx b/src/app/invite/InviteStatusCard.tsx index 3ecf16f5..6d7db4dc 100644 --- a/src/app/invite/InviteStatusCard.tsx +++ b/src/app/invite/InviteStatusCard.tsx @@ -17,11 +17,13 @@ import { useTranslations } from "next-intl"; type InviteStatusCardProps = { type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in"; token: string; + email?: string; }; export default function InviteStatusCard({ type, token, + email, }: InviteStatusCardProps) { const router = useRouter(); const api = createApiClient(useEnvContext()); @@ -29,12 +31,18 @@ export default function InviteStatusCard({ async function goToLogin() { await api.post("/auth/logout", {}); - router.push(`/auth/login?redirect=/invite?token=${token}`); + const redirectUrl = email + ? `/auth/login?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}` + : `/auth/login?redirect=/invite?token=${token}`; + router.push(redirectUrl); } async function goToSignup() { await api.post("/auth/logout", {}); - router.push(`/auth/signup?redirect=/invite?token=${token}`); + const redirectUrl = email + ? `/auth/signup?redirect=/invite?token=${token}&email=${encodeURIComponent(email)}` + : `/auth/signup?redirect=/invite?token=${token}`; + router.push(redirectUrl); } function renderBody() { diff --git a/src/app/invite/page.tsx b/src/app/invite/page.tsx index 014fb45b..0df7b810 100644 --- a/src/app/invite/page.tsx +++ b/src/app/invite/page.tsx @@ -14,6 +14,7 @@ export default async function InvitePage(props: { const params = await props.searchParams; const tokenParam = params.token as string; + const emailParam = params.email as string; if (!tokenParam) { redirect("/"); @@ -70,16 +71,22 @@ export default async function InvitePage(props: { const type = cardType(); if (!user && type === "user_does_not_exist") { - redirect(`/auth/signup?redirect=/invite?token=${params.token}`); + const redirectUrl = emailParam + ? `/auth/signup?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}` + : `/auth/signup?redirect=/invite?token=${params.token}`; + redirect(redirectUrl); } if (!user && type === "not_logged_in") { - redirect(`/auth/login?redirect=/invite?token=${params.token}`); + const redirectUrl = emailParam + ? `/auth/login?redirect=/invite?token=${params.token}&email=${encodeURIComponent(emailParam)}` + : `/auth/login?redirect=/invite?token=${params.token}`; + redirect(redirectUrl); } return ( <> - + ); }