mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-07 03:06:40 +00:00
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
This commit is contained in:
@@ -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) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
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) {
|
if (doEmail) {
|
||||||
await sendEmail(
|
await sendEmail(
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ type SignupFormProps = {
|
|||||||
redirect?: string;
|
redirect?: string;
|
||||||
inviteId?: string;
|
inviteId?: string;
|
||||||
inviteToken?: string;
|
inviteToken?: string;
|
||||||
|
emailParam?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formSchema = z
|
const formSchema = z
|
||||||
@@ -103,7 +104,8 @@ const formSchema = z
|
|||||||
export default function SignupForm({
|
export default function SignupForm({
|
||||||
redirect,
|
redirect,
|
||||||
inviteId,
|
inviteId,
|
||||||
inviteToken
|
inviteToken,
|
||||||
|
emailParam
|
||||||
}: SignupFormProps) {
|
}: SignupFormProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
@@ -118,7 +120,7 @@ export default function SignupForm({
|
|||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: emailParam || "",
|
||||||
password: "",
|
password: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
agreeToTerms: false
|
agreeToTerms: false
|
||||||
@@ -209,7 +211,10 @@ export default function SignupForm({
|
|||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("email")}</FormLabel>
|
<FormLabel>{t("email")}</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input
|
||||||
|
{...field}
|
||||||
|
disabled={!!emailParam}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -11,7 +11,10 @@ import { getTranslations } from "next-intl/server";
|
|||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
export default async function Page(props: {
|
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 searchParams = await props.searchParams;
|
||||||
const getUser = cache(verifySession);
|
const getUser = cache(verifySession);
|
||||||
@@ -69,6 +72,7 @@ export default async function Page(props: {
|
|||||||
redirect={redirectUrl}
|
redirect={redirectUrl}
|
||||||
inviteToken={inviteToken}
|
inviteToken={inviteToken}
|
||||||
inviteId={inviteId}
|
inviteId={inviteId}
|
||||||
|
emailParam={searchParams.email}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<p className="text-center text-muted-foreground mt-4">
|
<p className="text-center text-muted-foreground mt-4">
|
||||||
|
|||||||
@@ -17,11 +17,13 @@ import { useTranslations } from "next-intl";
|
|||||||
type InviteStatusCardProps = {
|
type InviteStatusCardProps = {
|
||||||
type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in";
|
type: "rejected" | "wrong_user" | "user_does_not_exist" | "not_logged_in";
|
||||||
token: string;
|
token: string;
|
||||||
|
email?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function InviteStatusCard({
|
export default function InviteStatusCard({
|
||||||
type,
|
type,
|
||||||
token,
|
token,
|
||||||
|
email,
|
||||||
}: InviteStatusCardProps) {
|
}: InviteStatusCardProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
@@ -29,12 +31,18 @@ export default function InviteStatusCard({
|
|||||||
|
|
||||||
async function goToLogin() {
|
async function goToLogin() {
|
||||||
await api.post("/auth/logout", {});
|
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() {
|
async function goToSignup() {
|
||||||
await api.post("/auth/logout", {});
|
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() {
|
function renderBody() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default async function InvitePage(props: {
|
|||||||
const params = await props.searchParams;
|
const params = await props.searchParams;
|
||||||
|
|
||||||
const tokenParam = params.token as string;
|
const tokenParam = params.token as string;
|
||||||
|
const emailParam = params.email as string;
|
||||||
|
|
||||||
if (!tokenParam) {
|
if (!tokenParam) {
|
||||||
redirect("/");
|
redirect("/");
|
||||||
@@ -70,16 +71,22 @@ export default async function InvitePage(props: {
|
|||||||
const type = cardType();
|
const type = cardType();
|
||||||
|
|
||||||
if (!user && type === "user_does_not_exist") {
|
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") {
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<InviteStatusCard type={type} token={tokenParam} />
|
<InviteStatusCard type={type} token={tokenParam} email={emailParam} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user