Feat - invite user to team flow improvements (#6725)
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 246 B |
After Width: | Height: | Size: 357 B |
After Width: | Height: | Size: 487 B |
Before Width: | Height: | Size: 467 B After Width: | Height: | Size: 193 B |
|
@ -1,5 +1,3 @@
|
|||
<svg stroke="#888888" fill="none" stroke-width="2.5" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>
|
||||
<polyline points="15 3 21 3 21 9"></polyline>
|
||||
<line x1="10" y1="14" x2="21" y2="3"></line>
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1.33333 6H10.6667M10.6667 6L6 1.33333M10.6667 6L6 10.6667" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 360 B After Width: | Height: | Size: 255 B |
After Width: | Height: | Size: 381 B |
|
@ -7,6 +7,7 @@
|
|||
"upgrade_now": "Upgrade now",
|
||||
"accept_invitation": "Accept Invitation",
|
||||
"calcom_explained": "{{appName}} provides scheduling infrastructure for absolutely everyone.",
|
||||
"calcom_explained_new_user": "Finish setting up your {{appName}} account! You’re just a few steps away from solving all your scheduling problems.",
|
||||
"have_any_questions": "Have questions? We're here to help.",
|
||||
"reset_password_subject": "{{appName}}: Reset password instructions",
|
||||
"event_declined_subject": "Declined: {{title}} at {{date}}",
|
||||
|
@ -1536,5 +1537,15 @@
|
|||
"delete_sso_configuration": "Delete {{connectionType}} configuration",
|
||||
"delete_sso_configuration_confirmation": "Yes, delete {{connectionType}} configuration",
|
||||
"delete_sso_configuration_confirmation_description": "Are you sure you want to delete the {{connectionType}} configuration? Your team members who use {{connectionType}} login will no longer be able to access Cal.com.",
|
||||
"email_no_user_cta":"Create your account",
|
||||
"email_user_cta":"View Invitation",
|
||||
"email_no_user_invite_heading":"You’ve been invited to join a team on {{appName}}",
|
||||
"email_no_user_invite_subheading":"{{invitedBy}} has invited you to join their team on {{appName}}. {{appName}} is the event-juggling scheduler that enables you and your team to schedule meetings without the email tennis.",
|
||||
"email_no_user_invite_steps_intro":"We’ll walk you through a few short steps and you’ll be enjoying stress free scheduling with your team in no time.",
|
||||
"email_no_user_step_one":"Choose your username",
|
||||
"email_no_user_step_two":"Connect your calendar account",
|
||||
"email_no_user_step_three":"Set your Availability",
|
||||
"email_no_user_step_four":"Join {{teamName}}",
|
||||
"email_no_user_signoff":"Happy Scheduling from the {{appName}} team",
|
||||
"impersonation_user_tip": "You are about to impersonate a user, which means you can make changes on their behalf. Please be careful."
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ export const CallToAction = (props: { label: string; href: string; secondary?: b
|
|||
margin: 0,
|
||||
textDecoration: "none",
|
||||
textTransform: "none",
|
||||
padding: "10px 16px",
|
||||
padding: "16px 24px",
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
msoPaddingAlt: "0px",
|
||||
|
|
|
@ -26,8 +26,8 @@ export const V2BaseEmailHtml = (props: {
|
|||
return (
|
||||
<Html>
|
||||
<EmailHead title={props.subject} />
|
||||
<body style={{ wordSpacing: "normal", backgroundColor: "#F5F5F5" }}>
|
||||
<div style={{ backgroundColor: "#F5F5F5" }}>
|
||||
<body style={{ wordSpacing: "normal", backgroundColor: "#F3F4F6" }}>
|
||||
<div style={{ backgroundColor: "#F3F4F6" }}>
|
||||
<RawHtml
|
||||
html={`<!--[if mso | IE]><table align="center" border="0" cellpadding="0" cellspacing="0" class="" style="width:600px;" width="600" ><tr><td style="line-height:0px;font-size:0px;mso-line-height-rule:exactly;"><![endif]-->`}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import type { TFunction } from "next-i18next";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
import { APP_NAME, BASE_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
import { APP_NAME, WEBAPP_URL, IS_PRODUCTION } from "@calcom/lib/constants";
|
||||
|
||||
import { V2BaseEmailHtml, CallToAction } from "../components";
|
||||
|
||||
|
@ -10,6 +11,7 @@ type TeamInvite = {
|
|||
to: string;
|
||||
teamName: string;
|
||||
joinLink: string;
|
||||
isCalcomMember: boolean;
|
||||
};
|
||||
|
||||
export const TeamInviteEmail = (
|
||||
|
@ -22,39 +24,100 @@ export const TeamInviteEmail = (
|
|||
team: props.teamName,
|
||||
appName: APP_NAME,
|
||||
})}>
|
||||
<p style={{ fontSize: "24px", marginBottom: "16px", textAlign: "center" }}>
|
||||
<>{props.language("email_no_user_invite_heading", { appName: APP_NAME })}</>
|
||||
</p>
|
||||
<img
|
||||
height="64"
|
||||
style={{
|
||||
borderRadius: "16px",
|
||||
height: "270px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
src={
|
||||
IS_PRODUCTION
|
||||
? BASE_URL + "/emails/teamCircle@2x.png"
|
||||
: "http://localhost:3000/emails/teamCircle@2x.png"
|
||||
? WEBAPP_URL + "/emails/calendar-email-hero.png"
|
||||
: "http://localhost:3000/emails/calendar-email-hero.png"
|
||||
}
|
||||
style={{
|
||||
border: "0",
|
||||
display: "block",
|
||||
outline: "none",
|
||||
textDecoration: "none",
|
||||
height: "64px",
|
||||
fontSize: "13px",
|
||||
marginBottom: "24px",
|
||||
}}
|
||||
width="64"
|
||||
alt=""
|
||||
/>
|
||||
<p style={{ fontSize: "24px", marginBottom: "16px" }}>
|
||||
<>
|
||||
{props.language("user_invited_you", {
|
||||
user: props.from,
|
||||
team: props.teamName,
|
||||
appName: APP_NAME,
|
||||
})}
|
||||
!
|
||||
</>
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
marginBottom: "32px",
|
||||
marginTop: "32px",
|
||||
lineHeightStep: "24px",
|
||||
}}>
|
||||
<>{props.language("email_no_user_invite_subheading", { invitedBy: props.from, appName: APP_NAME })}</>
|
||||
</p>
|
||||
<p style={{ fontWeight: 400, lineHeight: "24px", marginBottom: "32px" }}>
|
||||
<>{props.language("calcom_explained", { appName: APP_NAME })}</>
|
||||
<div style={{ display: "flex", justifyContent: "center" }}>
|
||||
<CallToAction
|
||||
label={props.language(props.isCalcomMember ? "email_user_cta" : "email_no_user_cta")}
|
||||
href={props.joinLink}
|
||||
/>
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
marginBottom: "32px",
|
||||
marginTop: "48px",
|
||||
lineHeightStep: "24px",
|
||||
}}>
|
||||
<>{props.language("email_no_user_invite_steps_intro")}</>
|
||||
</p>
|
||||
<CallToAction label={props.language("accept_invitation")} href={props.joinLink} />
|
||||
|
||||
{!props.isCalcomMember && (
|
||||
<div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
|
||||
<EmailStep
|
||||
translationString={props.language("email_no_user_step_one")}
|
||||
iconsrc={
|
||||
IS_PRODUCTION
|
||||
? WEBAPP_URL + "/emails/choose-username@2x.png"
|
||||
: "http://localhost:3000/emails/choose-username@2x.png"
|
||||
}
|
||||
/>
|
||||
<EmailStep
|
||||
translationString={props.language("email_no_user_step_two")}
|
||||
iconsrc={
|
||||
IS_PRODUCTION
|
||||
? WEBAPP_URL + "/emails/calendar@2x.png"
|
||||
: "http://localhost:3000/emails/calendar@2x.png"
|
||||
}
|
||||
/>
|
||||
<EmailStep
|
||||
translationString={props.language("email_no_user_step_three")}
|
||||
iconsrc={
|
||||
IS_PRODUCTION
|
||||
? WEBAPP_URL + "/emails/clock@2x.png"
|
||||
: "http://localhost:3000/emails/clock@2x.png"
|
||||
}
|
||||
/>
|
||||
<EmailStep
|
||||
translationString={props.language("email_no_user_step_four", { teamName: props.teamName })}
|
||||
iconsrc={
|
||||
IS_PRODUCTION
|
||||
? WEBAPP_URL + "/emails/user-check@2x.png"
|
||||
: "http://localhost:3000/emails/user-check@2x.png"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="">
|
||||
<p
|
||||
style={{
|
||||
fontWeight: 400,
|
||||
lineHeight: "24px",
|
||||
marginBottom: "32px",
|
||||
marginTop: "32px",
|
||||
lineHeightStep: "24px",
|
||||
}}>
|
||||
<>{props.language("email_no_user_signoff", { appName: APP_NAME })}</>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{ borderTop: "1px solid #E1E1E1", marginTop: "32px", paddingTop: "32px" }}>
|
||||
<p style={{ fontWeight: 400, margin: 0 }}>
|
||||
|
@ -70,3 +133,34 @@ export const TeamInviteEmail = (
|
|||
</V2BaseEmailHtml>
|
||||
);
|
||||
};
|
||||
|
||||
const EmailStep = (props: { translationString: string; iconsrc: string }) => {
|
||||
return (
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<div
|
||||
style={{
|
||||
backgroundColor: "#E5E7EB",
|
||||
borderRadius: "48px",
|
||||
height: "48px",
|
||||
width: "48px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
color: "white",
|
||||
marginRight: "16px",
|
||||
}}>
|
||||
<img src={props.iconsrc} alt="#" style={{ height: "24px", width: "auto" }} />
|
||||
</div>
|
||||
<p
|
||||
style={{
|
||||
fontStyle: "normal",
|
||||
fontWeight: 500,
|
||||
fontSize: "18px",
|
||||
lineHeight: "20px",
|
||||
color: "#black",
|
||||
}}>
|
||||
<>{props.translationString}</>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -11,6 +11,7 @@ export type TeamInvite = {
|
|||
to: string;
|
||||
teamName: string;
|
||||
joinLink: string;
|
||||
isCalcomMember: boolean;
|
||||
};
|
||||
|
||||
export default class TeamInviteEmail extends BaseEmail {
|
||||
|
|
|
@ -153,7 +153,7 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
|
|||
color="primary"
|
||||
className="ltr:ml-2 ltr:mr-2 rtl:ml-2"
|
||||
data-testid="invite-new-member-button">
|
||||
{t("invite_new_member")}
|
||||
{t("invite")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Form>
|
||||
|
|
|
@ -279,8 +279,6 @@ export const viewerTeamsRouter = router({
|
|||
},
|
||||
});
|
||||
|
||||
let inviteeUserId: number | undefined = invitee?.id;
|
||||
|
||||
if (!invitee) {
|
||||
// liberal email match
|
||||
|
||||
|
@ -291,7 +289,7 @@ export const viewerTeamsRouter = router({
|
|||
});
|
||||
|
||||
// valid email given, create User and add to team
|
||||
const user = await ctx.prisma.user.create({
|
||||
await ctx.prisma.user.create({
|
||||
data: {
|
||||
email: input.usernameOrEmail,
|
||||
invitedTo: input.teamId,
|
||||
|
@ -303,7 +301,6 @@ export const viewerTeamsRouter = router({
|
|||
},
|
||||
},
|
||||
});
|
||||
inviteeUserId = user.id;
|
||||
|
||||
const token: string = randomBytes(32).toString("hex");
|
||||
|
||||
|
@ -314,7 +311,6 @@ export const viewerTeamsRouter = router({
|
|||
expires: new Date(new Date().setHours(168)), // +1 week
|
||||
},
|
||||
});
|
||||
|
||||
if (ctx?.user?.name && team?.name) {
|
||||
await sendTeamInviteEmail({
|
||||
language: translation,
|
||||
|
@ -322,6 +318,7 @@ export const viewerTeamsRouter = router({
|
|||
to: input.usernameOrEmail,
|
||||
teamName: team.name,
|
||||
joinLink: `${WEBAPP_URL}/signup?token=${token}&callbackUrl=/teams`,
|
||||
isCalcomMember: false,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
@ -357,6 +354,7 @@ export const viewerTeamsRouter = router({
|
|||
to: sendTo,
|
||||
teamName: team.name,
|
||||
joinLink: WEBAPP_URL + "/settings/teams",
|
||||
isCalcomMember: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|