Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new org onboarding wizard #13139

Merged
merged 55 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
3e86dfd
added layouts for moving teams step
PeerRich Jan 10, 2024
09b3424
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
emrysal Jan 10, 2024
594bc9f
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
PeerRich Jan 16, 2024
613105f
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
PeerRich Jan 17, 2024
f5fa46c
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
PeerRich Jan 18, 2024
a0ab667
added admin section for org creation step
PeerRich Jan 18, 2024
a49da46
extended org creation to also move existing teams
PeerRich Jan 18, 2024
ef4e4ba
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
PeerRich Jan 23, 2024
88904d0
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
PeerRich Jan 29, 2024
23e8ecd
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
hariombalhara Feb 6, 2024
cc0f200
wip
hariombalhara Feb 7, 2024
553b551
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
hariombalhara Feb 17, 2024
755f0fb
wip
hariombalhara Feb 19, 2024
3af2f04
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
hariombalhara Feb 28, 2024
eb0ba77
wip
hariombalhara Feb 28, 2024
fea6fd7
further changes
hariombalhara Mar 7, 2024
e83e48a
Add checkout for org in onboarding
hariombalhara Mar 8, 2024
a4861d5
Fix ts errors
hariombalhara Mar 8, 2024
0a86e78
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
hariombalhara Mar 8, 2024
ef565fe
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
hariombalhara Mar 12, 2024
11e397c
Self review feedback
hariombalhara Mar 12, 2024
87dff01
Merge remote-tracking branch 'origin/13135-cal-2888-new-org-migration…
hariombalhara Mar 12, 2024
978c13b
Self review addressed
hariombalhara Mar 13, 2024
2442795
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
hariombalhara Mar 13, 2024
a5fc88a
Fix unit tests
hariombalhara Mar 13, 2024
274266e
Fix ts error
hariombalhara Mar 13, 2024
0a1847b
Seans feedback addressed
hariombalhara Mar 13, 2024
2743d29
feat: fix correct accounts pending
sean-brydon Mar 14, 2024
456a53b
fix: unit tests for new invite member permissions
sean-brydon Mar 14, 2024
91ea7b8
tests: org admin onboarding tests for existing user
sean-brydon Mar 14, 2024
02b3153
tests: Inital user self serve flow
sean-brydon Mar 14, 2024
60cd45f
chore: update teamAndUserFixture to create X amount of teams
sean-brydon Mar 18, 2024
5ef33ae
chore: add testId to card actionButton
sean-brydon Mar 18, 2024
ac4ff80
test: add test-Id to continue or checkout button
sean-brydon Mar 18, 2024
87992b4
tests: add tests for migrating existing teams
sean-brydon Mar 18, 2024
fc89050
feat: match new designs
sean-brydon Mar 25, 2024
330e514
Merge remote-tracking branch 'origin/main' into 13135-cal-2888-new-or…
sean-brydon Mar 25, 2024
a8de971
fix: isAdminCheck
sean-brydon Mar 26, 2024
7d72736
chore: fix pricing copy
sean-brydon Mar 26, 2024
c2ee412
merge: main into branch
sean-brydon Mar 27, 2024
ed1d547
fix: flacky tests
sean-brydon Mar 27, 2024
6b264a8
Fix tests?
joeauyeung Mar 28, 2024
acf11bb
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
joeauyeung Mar 28, 2024
a206b14
More test fixes
joeauyeung Mar 28, 2024
70d422f
Merge branch '13135-cal-2888-new-org-migration-path' of https://githu…
joeauyeung Mar 28, 2024
dfc4ef7
Check all checkboxes
joeauyeung Mar 28, 2024
af6ed4b
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
joeauyeung Mar 28, 2024
49928eb
Fix type error
hariombalhara Mar 30, 2024
0183958
Fix failing test and typescript issues
hariombalhara Mar 30, 2024
6d0d0d9
Fix unpaid org allowing auto-add users
hariombalhara Mar 30, 2024
6ded450
Add self-serve flag
hariombalhara Mar 30, 2024
bd01671
Skip tests
hariombalhara Mar 30, 2024
10c26c0
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
joeauyeung Mar 30, 2024
4368116
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
hariombalhara Apr 2, 2024
631141b
Merge branch 'main' into 13135-cal-2888-new-org-migration-path
zomars Apr 3, 2024
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
4 changes: 3 additions & 1 deletion apps/api/v1/pages/api/teams/_post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";

import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
Expand Down Expand Up @@ -197,8 +198,9 @@ const generateTeamCheckoutSession = async ({
customer_update: {
address: "auto",
},
// Disabled when testing locally as usually developer doesn't setup Tax in Stripe Test mode
automatic_tax: {
enabled: true,
enabled: IS_PRODUCTION,
},
metadata: {
pendingPaymentTeamId,
Expand Down

This file was deleted.

19 changes: 2 additions & 17 deletions apps/web/pages/settings/organizations/[id]/add-teams.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use client";

import { redirect, useRouter } from "next/navigation";

import { AddNewTeamsForm } from "@calcom/features/ee/organizations/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta, WizardLayout } from "@calcom/ui";
Expand All @@ -22,16 +20,8 @@ const AddNewTeamsPage = () => {
};

AddNewTeamsPage.getLayout = (page: React.ReactElement) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const router = useRouter();

return (
<WizardLayout
currentStep={5}
maxSteps={5}
isOptionalCallback={() => {
Copy link
Member

Choose a reason for hiding this comment

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

It is not an optional step now. User has to click the Continue/Checkout button

router.push(`/event-types`);
}}>
<WizardLayout currentStep={5} maxSteps={5}>
{page}
</WizardLayout>
);
Expand All @@ -41,12 +31,7 @@ AddNewTeamsPage.PageWrapper = PageWrapper;

export const WrapperAddNewTeamsPage = (page: React.ReactElement) => {
return (
<WizardLayoutAppDir
currentStep={5}
maxSteps={5}
isOptionalCallback={() => {
redirect(`/event-types`);
}}>
<WizardLayoutAppDir currentStep={5} maxSteps={5}>
{page}
</WizardLayoutAppDir>
);
Expand Down
39 changes: 0 additions & 39 deletions apps/web/pages/settings/organizations/[id]/set-password.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion apps/web/pages/settings/organizations/new/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

import PageWrapper from "@components/PageWrapper";

const CreateNewOrganizationPage = ({ querySlug }: inferSSRProps<typeof getServerSideProps>) => {

Check warning on line 13 in apps/web/pages/settings/organizations/new/index.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

apps/web/pages/settings/organizations/new/index.tsx#L13

[@typescript-eslint/no-unused-vars] 'querySlug' is defined but never used. Allowed unused args must match /^_/u.
const { t } = useLocale();
return (
<LicenseRequired>
<Meta title={t("set_up_your_organization")} description={t("organizations_description")} />
<CreateANewOrganizationForm slug={querySlug} />
<CreateANewOrganizationForm />
</LicenseRequired>
);
};
Expand Down
202 changes: 107 additions & 95 deletions apps/web/playwright/fixtures/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,22 @@
isOrgVerified,
hasSubteam,
organizationId,
isDnsSetup,
index,
}: {
user: { id: number; email: string; username: string | null; role?: MembershipRole };
isUnpublished?: boolean;
isOrg?: boolean;
isOrgVerified?: boolean;
isDnsSetup?: boolean;
hasSubteam?: true;
organizationId?: number | null;
index?: number;
},
workerInfo: WorkerInfo
) => {
const slug = `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}`;
const slugIndex = index ? `-count-${index}` : "";
Copy link
Member

Choose a reason for hiding this comment

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

If an index is passed in (when called in loop we append it to the slug to prevent duplicates for this run)

const slug = `${isOrg ? "org" : "team"}-${workerInfo.workerIndex}-${Date.now()}${slugIndex}`;
const data: PrismaType.TeamCreateInput = {
name: `user-id-${user.id}'s ${isOrg ? "Org" : "Team"}`,
isOrganization: isOrg,
Expand All @@ -128,9 +133,9 @@
if (isOrg) {
data.organizationSettings = {
create: {
isOrganizationVerified: !!isOrgVerified,
orgAutoAcceptEmail: user.email.split("@")[1],
isOrganizationConfigured: false,
isOrganizationVerified: !!isOrgVerified,
isOrganizationConfigured: isDnsSetup,
},
};
}
Expand Down Expand Up @@ -211,6 +216,7 @@
scenario: {
seedRoutingForms?: boolean;
hasTeam?: true;
numberOfTeams?: number;
teamRole?: MembershipRole;
teammates?: CustomUserOpts[];
schedulingType?: SchedulingType;
Expand All @@ -219,6 +225,7 @@
teamEventLength?: number;
isOrg?: boolean;
isOrgVerified?: boolean;
isDnsSetup?: boolean;
hasSubteam?: true;
isUnpublished?: true;
} = {}
Expand Down Expand Up @@ -382,103 +389,108 @@
include: userIncludes,
});
if (scenario.hasTeam) {
const team = await createTeamAndAddUser(
{
user: {
id: user.id,
email: user.email,
username: user.username,
role: scenario.teamRole || "OWNER",
},
isUnpublished: scenario.isUnpublished,
isOrg: scenario.isOrg,
isOrgVerified: scenario.isOrgVerified,
hasSubteam: scenario.hasSubteam,
organizationId: opts?.organizationId,
},
workerInfo
);
store.teams.push(team);
const teamEvent = await createTeamEventType(user, team, scenario);
if (scenario.teammates) {
// Create Teammate users
const teamMates = [];
for (const teammateObj of scenario.teammates) {
const teamUser = await prisma.user.create({
data: createUser(workerInfo, teammateObj),
});

// Add teammates to the team
await prisma.membership.create({
data: {
teamId: team.id,
userId: teamUser.id,
role: MembershipRole.MEMBER,
accepted: true,
},
});

// Add teammate to the host list of team event
await prisma.host.create({
data: {
userId: teamUser.id,
eventTypeId: teamEvent.id,
isFixed: scenario.schedulingType === SchedulingType.COLLECTIVE ? true : false,
const numberOfTeams = scenario.numberOfTeams || 1;
for (let i = 0; i < numberOfTeams; i++) {
Copy link
Member

Choose a reason for hiding this comment

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

If a number of teams is passed in -> create X amount of them

const team = await createTeamAndAddUser(
{
user: {
id: user.id,
email: user.email,
username: user.username,
role: scenario.teamRole || "OWNER",
},
});

const teammateFixture = createUserFixture(
await prisma.user.findUniqueOrThrow({
where: { id: teamUser.id },
include: userIncludes,
}),
store.page
);
teamMates.push(teamUser);
store.users.push(teammateFixture);
}
// Add Teammates to OrgUsers
if (scenario.isOrg) {
const orgProfilesCreate = teamMates
.map((teamUser) => ({
user: {
connect: {
id: teamUser.id,
},
isUnpublished: scenario.isUnpublished,
isOrg: scenario.isOrg,
isOrgVerified: scenario.isOrgVerified,
isDnsSetup: scenario.isDnsSetup,
hasSubteam: scenario.hasSubteam,
organizationId: opts?.organizationId,
},
workerInfo
);
store.teams.push(team);
const teamEvent = await createTeamEventType(user, team, scenario);
if (scenario.teammates) {
// Create Teammate users
const teamMates = [];
for (const teammateObj of scenario.teammates) {
const teamUser = await prisma.user.create({
data: createUser(workerInfo, teammateObj),
});

// Add teammates to the team
await prisma.membership.create({
data: {
teamId: team.id,
userId: teamUser.id,
role: MembershipRole.MEMBER,
accepted: true,
},
uid: v4(),
username: teamUser.username || teamUser.email.split("@")[0],
}))
.concat([
{
user: { connect: { id: user.id } },
uid: v4(),
username: user.username || user.email.split("@")[0],
});

// Add teammate to the host list of team event
await prisma.host.create({
data: {
userId: teamUser.id,
eventTypeId: teamEvent.id,
isFixed: scenario.schedulingType === SchedulingType.COLLECTIVE ? true : false,
},
]);
});

const teammateFixture = createUserFixture(
await prisma.user.findUniqueOrThrow({
where: { id: teamUser.id },
include: userIncludes,
}),
store.page
);
teamMates.push(teamUser);
store.users.push(teammateFixture);
}
// Add Teammates to OrgUsers
if (scenario.isOrg) {
const orgProfilesCreate = teamMates
.map((teamUser) => ({
user: {
connect: {
id: teamUser.id,
},
},
uid: v4(),
username: teamUser.username || teamUser.email.split("@")[0],
}))
.concat([
{
user: { connect: { id: user.id } },
uid: v4(),
username: user.username || user.email.split("@")[0],
},
]);

const existingProfiles = await prisma.profile.findMany({
where: {
userId: _user.id,
},
});
const existingProfiles = await prisma.profile.findMany({
where: {
userId: _user.id,
},
});

await prisma.team.update({
where: {
id: team.id,
},
data: {
orgProfiles: _user.profiles.length
? {
connect: _user.profiles.map((profile) => ({ id: profile.id })),
}
: {
create: orgProfilesCreate.filter(
(profile) => !existingProfiles.map((p) => p.userId).includes(profile.user.connect.id)
),
},
},
});
await prisma.team.update({
where: {
id: team.id,
},
data: {
orgProfiles: _user.profiles.length
? {
connect: _user.profiles.map((profile) => ({ id: profile.id })),
}
: {
create: orgProfilesCreate.filter(
(profile) =>
!existingProfiles.map((p) => p.userId).includes(profile.user.connect.id)
),
},
},
});
}
}
}
}
Expand Down Expand Up @@ -842,7 +854,7 @@
) {
const csrfToken = await page
.context()
.request.get("/api/auth/csrf")

Check failure on line 857 in apps/web/playwright/fixtures/users.ts

View workflow job for this annotation

GitHub Actions / E2E tests / E2E tests (3/5)

[@calcom/web] › apps/web/playwright/integrations-stripe.e2e.ts:190:7 › Stripe integration › Paid booking should be able to be cancelled

2) [@***com/web] › apps/web/playwright/integrations-stripe.e2e.ts:190:7 › Stripe integration › Paid booking should be able to be cancelled apiRequestContext.get: read ECONNRESET =========================== logs =========================== → GET http://***:3000/api/auth/csrf user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.19 Safari/537.36 accept: */* accept-encoding: gzip,deflate,br ← 408 Request Timeout connection: close ============================================================ at apps/web/playwright/fixtures/users.ts:857 855 | const csrfToken = await page 856 | .context() > 857 | .request.get("/api/auth/csrf") | ^ 858 | .then((response) => response.json()) 859 | .then((json) => json.csrfToken); 860 | const data = { at apiLogin (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/fixtures/users.ts:857:14) at Object.apiLogin (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/fixtures/users.ts:588:7) at /home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/integrations-stripe.e2e.ts:193:5

Check failure on line 857 in apps/web/playwright/fixtures/users.ts

View workflow job for this annotation

GitHub Actions / E2E tests / E2E tests (1/5)

[@calcom/web] › apps/web/playwright/app-store.e2e.ts:12:7 › App Store - Authed -- legacy › should render /apps page

1) [@***com/web] › apps/web/playwright/app-store.e2e.ts:12:7 › App Store - Authed -- legacy › should render /apps page apiRequestContext.get: read ECONNRESET =========================== logs =========================== → GET http://***:3000/api/auth/csrf user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.5563.19 Safari/537.36 accept: */* accept-encoding: gzip,deflate,br ← 408 Request Timeout connection: close ============================================================ at apps/web/playwright/fixtures/users.ts:857 855 | const csrfToken = await page 856 | .context() > 857 | .request.get("/api/auth/csrf") | ^ 858 | .then((response) => response.json()) 859 | .then((json) => json.csrfToken); 860 | const data = { at apiLogin (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/fixtures/users.ts:857:14) at Object.apiLogin (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/fixtures/users.ts:588:7) at /home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/app-store.e2e.ts:16:5
.then((response) => response.json())
.then((json) => json.csrfToken);
const data = {
Expand Down Expand Up @@ -877,7 +889,7 @@
await page.fill('[name="name"]', "Stripe Stripeson");
await page.fill('[name="email"]', "test@example.com");

await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]);

Check failure on line 892 in apps/web/playwright/fixtures/users.ts

View workflow job for this annotation

GitHub Actions / E2E tests / E2E tests (3/5)

[@calcom/web] › apps/web/playwright/integrations-stripe.e2e.ts:130:7 › Stripe integration › Can book a paid booking

1) [@***com/web] › apps/web/playwright/integrations-stripe.e2e.ts:130:7 › Stripe integration › Can book a paid booking page.waitForURL: Timeout 30000ms exceeded. =========================== logs =========================== waiting for navigation to "/payment/*" until "load" navigated to "http://***:3000/booking/11eaKsLy6VYDxn5MLUvc1o?isSuccessBookingPage=true&email=test%40example.com&eventTypeSlug=paid" ============================================================ at apps/web/playwright/fixtures/users.ts:892 890 | await page.fill('[name="email"]', "test@example.com"); 891 | > 892 | await Promise.all([page.waitForURL("/payment/*"), page.press('[name="email"]', "Enter")]); | ^ 893 | 894 | await makePaymentUsingStripe(page); 895 | } at bookAndPayEvent (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/fixtures/users.ts:892:27) at /home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/integrations-stripe.e2e.ts:138:5

await makePaymentUsingStripe(page);
}
Expand Down
2 changes: 2 additions & 0 deletions apps/web/playwright/lib/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
previewLink = `/forms/${formId}`;
}

await page.goto(`${previewLink}${queryString ? `?${queryString}` : ""}`);

Check failure on line 199 in apps/web/playwright/lib/testUtils.ts

View workflow job for this annotation

GitHub Actions / E2E tests / E2E tests (5/5)

[@calcom/web] › apps/web/playwright/webhook.e2e.ts:611:7 › FORM_SUBMITTED › on submitting team form

1) [@***com/web] › apps/web/playwright/webhook.e2e.ts:611:7 › FORM_SUBMITTED › on submitting team form, triggers team webhook page.goto: net::ERR_ABORTED at http://***:3000/forms/cluj5blxv0001jxdnouapw8yk =========================== logs =========================== navigating to "http://***:3000/forms/cluj5blxv0001jxdnouapw8yk", waiting until "load" ============================================================ at apps/web/playwright/lib/testUtils.ts:199 197 | } 198 | > 199 | await page.goto(`${previewLink}${queryString ? `?${queryString}` : ""}`); | ^ 200 | 201 | // HACK: There seems to be some issue with the inputs to the form getting reset if we don't wait. 202 | await new Promise((resolve) => setTimeout(resolve, 1000)); at gotoRoutingLink (/home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/lib/testUtils.ts:199:14) at /home/runner/actions-runner/_work/***.com/***.com/apps/web/playwright/webhook.e2e.ts:640:26

// HACK: There seems to be some issue with the inputs to the form getting reset if we don't wait.
await new Promise((resolve) => setTimeout(resolve, 1000));
Expand Down Expand Up @@ -347,6 +347,8 @@
await page.fill("[name=cardExpiry]", "12/30");
await page.fill("[name=cardCvc]", "111");
await page.fill("[name=billingName]", "Stripe Stripeson");
await page.selectOption("[name=billingCountry]", "US");
await page.fill("[name=billingPostalCode]", "12345");
Comment on lines +350 to +351
Copy link
Member

Choose a reason for hiding this comment

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

Stripe seems to have added this in the checkout page?

await page.click(".SubmitButton--complete-Shimmer");
}

Expand Down
Loading
Loading