Skip to content

Commit

Permalink
Redirect to sign up when using invitation link (#5770)
Browse files Browse the repository at this point in the history
* Redirect to sing up when using invitation link

* Add login_hint on invitation flow

* Add horizontal static icon + placeholders

* 🙈

* Update login-error.tsx

* ✨

* Improve guessFirstandLastNameFromFullName

* 🙈

* 👯

* 🙈
  • Loading branch information
flvndvd authored Jun 21, 2024
1 parent 35b6da8 commit be223b7
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 41 deletions.
6 changes: 3 additions & 3 deletions front/lib/iam/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { renderUserType } from "@app/lib/api/user";
import type { ExternalUser, SessionWithUser } from "@app/lib/iam/provider";
import { User } from "@app/lib/models/user";
import { ServerSideTracking } from "@app/lib/tracking/server";
import { guessFirstandLastNameFromFullName } from "@app/lib/user";
import { guessFirstAndLastNameFromFullName } from "@app/lib/user";
import { generateModelSId } from "@app/lib/utils";

interface LegacyProviderInfo {
Expand Down Expand Up @@ -115,7 +115,7 @@ export async function createOrUpdateUser(
user.firstName = externalUser.given_name;
user.lastName = externalUser.family_name;
} else {
const { firstName, lastName } = guessFirstandLastNameFromFullName(
const { firstName, lastName } = guessFirstAndLastNameFromFullName(
externalUser.name
);
user.firstName = firstName;
Expand All @@ -127,7 +127,7 @@ export async function createOrUpdateUser(

return { user: renderUserType(user), created: false };
} else {
const { firstName, lastName } = guessFirstandLastNameFromFullName(
const { firstName, lastName } = guessFirstAndLastNameFromFullName(
externalUser.name
);

Expand Down
17 changes: 7 additions & 10 deletions front/lib/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,13 @@ export async function setUserMetadataFromClient(metadata: UserMetadataType) {
return;
}

export const guessFirstandLastNameFromFullName = (
export const guessFirstAndLastNameFromFullName = (
fullName: string
): { firstName: string; lastName: string | null } => {
const nameParts = fullName.split(" ");

if (nameParts.length > 1) {
const firstName = nameParts.shift() || fullName;
const lastName = nameParts.join(" ");
return { firstName, lastName };
} else {
return { firstName: fullName, lastName: null };
}
const [prefixPart] = fullName.split("@"); // Ignore everything after '@'.
const nameParts = prefixPart.split(/[\s.]+/); // Split on spaces and dots.

const [firstName = prefixPart, ...lastName] = nameParts;

return { firstName, lastName: lastName.join(" ") || null };
};
4 changes: 2 additions & 2 deletions front/migrations/20231017_user_first_and_last_name.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { User } from "@app/lib/models/user";
import { guessFirstandLastNameFromFullName } from "@app/lib/user";
import { guessFirstAndLastNameFromFullName } from "@app/lib/user";

async function main() {
const users: User[] = await User.findAll({
Expand All @@ -25,7 +25,7 @@ async function main() {
chunk.map((u: User) => {
return (async () => {
if (!u.firstName) {
const { firstName, lastName } = guessFirstandLastNameFromFullName(
const { firstName, lastName } = guessFirstAndLastNameFromFullName(
u.name
);
return u.update({
Expand Down
28 changes: 20 additions & 8 deletions front/pages/api/auth/[auth0].ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ import {
import type { NextApiRequest, NextApiResponse } from "next";

import config from "@app/lib/api/config";
import { isEmailValid } from "@app/lib/utils";

const isString = (value: unknown): value is string => typeof value === "string";

export default handleAuth({
login: handleLogin((req) => {
let connection: string | undefined = undefined;
if ("query" in req && req.query.connection) {
connection =
typeof req.query.connection === "string"
? req.query.connection
: undefined;
}
const { connection, screen_hint, login_hint } =
"query" in req
? req.query
: {
connection: undefined,
login_hint: undefined,
screen_hint: undefined,
};

const defaultAuthorizationParams: Partial<
LoginOptions["authorizationParams"]
Expand All @@ -28,10 +32,18 @@ export default handleAuth({
};

// Set the Auth0 connection based on the provided connection param, redirecting the user to the correct screen.
if (connection) {
if (isString(connection)) {
defaultAuthorizationParams.connection = connection;
}

if (isString(screen_hint) && screen_hint === "signup") {
defaultAuthorizationParams.screen_hint = screen_hint;
}

if (isString(login_hint) && isEmailValid(login_hint)) {
defaultAuthorizationParams.login_hint = login_hint;
}

return {
authorizationParams: defaultAuthorizationParams,
returnTo: "/api/login",
Expand Down
18 changes: 17 additions & 1 deletion front/pages/login-error.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { Button, Icon, LogoSquareColorLogo, Page } from "@dust-tt/sparkle";
import {
Button,
Icon,
LoginIcon,
LogoSquareColorLogo,
Page,
} from "@dust-tt/sparkle";
import type { InferGetServerSidePropsType } from "next";
import Link from "next/link";

Expand Down Expand Up @@ -84,6 +90,16 @@ function getErrorMessage(domain: string | null, reason: string | null) {
<br />
Check you spam folder.
</p>

<Button
variant="tertiary"
size="sm"
label="Sign in"
icon={LoginIcon}
onClick={() => {
window.location.href = `/api/auth/login?returnTo=/api/login`;
}}
/>
</>
);

Expand Down
58 changes: 43 additions & 15 deletions front/pages/w/[wId]/join.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
getWorkspaceInfos,
getWorkspaceVerifiedDomain,
} from "@app/lib/api/workspace";
import { getPendingMembershipInvitationForToken } from "@app/lib/iam/invitations";
import { makeGetServerSidePropsRequirementsWrapper } from "@app/lib/iam/session";

const { URL = "", GA_TRACKING_ID = "" } = process.env;
Expand Down Expand Up @@ -39,24 +40,27 @@ type OnboardingType =
export const getServerSideProps = makeGetServerSidePropsRequirementsWrapper({
requireUserPrivilege: "none",
})<{
baseUrl: string;
gaTrackingId: string;
invitationEmail: string | null;
onboardingType: OnboardingType;
workspace: LightWorkspaceType;
signUpCallbackUrl: string;
gaTrackingId: string;
baseUrl: string;
workspace: LightWorkspaceType;
}>(async (context) => {
const wId = context.query.wId as string;
if (!wId) {
return {
notFound: true,
};
}

const workspace = await getWorkspaceInfos(wId);
if (!workspace) {
return {
notFound: true,
};
}

const workspaceDomain = await getWorkspaceVerifiedDomain(workspace);

const cId = typeof context.query.cId === "string" ? context.query.cId : null;
Expand All @@ -82,13 +86,33 @@ export const getServerSideProps = makeGetServerSidePropsRequirementsWrapper({
}

let signUpCallbackUrl: string | undefined = undefined;
let invitationEmail: string | null = null;
switch (onboardingType) {
case "domain_conversation_link":
signUpCallbackUrl = `/api/login?wId=${wId}&cId=${cId}&join=true`;
break;
case "email_invite":
case "email_invite": {
signUpCallbackUrl = `/api/login?inviteToken=${token}`;
const res = await getPendingMembershipInvitationForToken(
token ?? undefined
);
// Redirect to login error page with specific reason
// if token validation fails.
if (res.isErr()) {
return {
redirect: {
destination: `/api/auth/logout?returnTo=/login-error?reason=${res.error.code}`,
permanent: false,
},
};
}

if (res.value) {
invitationEmail = res.value.inviteEmail;
}
break;
}

case "domain_invite_link":
signUpCallbackUrl = `/api/login?wId=${wId}`;
break;
Expand All @@ -100,21 +124,29 @@ export const getServerSideProps = makeGetServerSidePropsRequirementsWrapper({

return {
props: {
onboardingType: onboardingType,
workspace,
signUpCallbackUrl: signUpCallbackUrl,
baseUrl: URL,
gaTrackingId: GA_TRACKING_ID,
invitationEmail,
onboardingType,
signUpCallbackUrl,
workspace,
},
};
});

export default function Join({
gaTrackingId,
invitationEmail,
onboardingType,
workspace,
signUpCallbackUrl,
gaTrackingId,
workspace,
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
let signUpUrl = `/api/auth/login?returnTo=${signUpCallbackUrl}&screen_hint=signup`;

if (invitationEmail) {
signUpUrl += `&login_hint=${encodeURIComponent(invitationEmail)}`;
}

return (
<OnboardingLayout
owner={workspace}
Expand All @@ -126,9 +158,7 @@ export default function Join({
size="sm"
label="Sign up"
icon={LoginIcon}
onClick={() =>
(window.location.href = `/api/auth/login?returnTo=${signUpCallbackUrl}`)
}
onClick={() => (window.location.href = signUpUrl)}
/>
}
>
Expand Down Expand Up @@ -174,9 +204,7 @@ export default function Join({
size="sm"
label="Sign up"
icon={LoginIcon}
onClick={() =>
(window.location.href = `/api/auth/login?returnTo=${signUpCallbackUrl}`)
}
onClick={() => (window.location.href = signUpUrl)}
/>
</div>
<div className="flex flex-col gap-3 pb-20">
Expand Down
4 changes: 2 additions & 2 deletions front/pages/w/[wId]/welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ export default function Welcome({
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<Input
name="firstName"
placeholder=""
placeholder="First Name"
value={firstName}
onChange={setFirstName}
/>
<Input
name="lastName"
placeholder=""
placeholder="Last Name"
value={lastName}
onChange={setLastName}
/>
Expand Down
Binary file added front/public/static/DustHorizontalIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit be223b7

Please sign in to comment.