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: platform onboarding flow and dashboard #14721

Merged
merged 99 commits into from May 9, 2024
Merged
Show file tree
Hide file tree
Changes from 92 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
602ed87
add endpoint to fetch managed user from client id
Ryukemeister Apr 23, 2024
ec41c68
update typings
Ryukemeister Apr 23, 2024
da31c11
minor tweaks
Ryukemeister Apr 23, 2024
7bc4a6b
custom hook to fetch managed users from client id
Ryukemeister Apr 23, 2024
fc59a7f
add translations for platform onboarding
Ryukemeister Apr 23, 2024
bbf2c8c
add isPlatformOrg boolean to figure out which is platform and which i…
Ryukemeister Apr 23, 2024
78d3823
set isPlatform hook based on data obtained from org
Ryukemeister Apr 23, 2024
b239856
add limitWidth prop to control component width
Ryukemeister Apr 23, 2024
20f433c
add props to shell to make sidebar display different tabs based on if…
Ryukemeister Apr 23, 2024
51f56b0
platform related pages
Ryukemeister Apr 23, 2024
15c5189
fix merge conflicts
Ryukemeister Apr 23, 2024
0dcf188
fix merge conflicts
Ryukemeister Apr 23, 2024
793efbf
platform oauth client form and card
Ryukemeister Apr 23, 2024
1a650d2
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 23, 2024
2745c75
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 23, 2024
b4a5724
remove everything related to platform from organization
Ryukemeister Apr 24, 2024
fca0cf7
update oauth client card and form
Ryukemeister Apr 24, 2024
b038eff
fixup
Ryukemeister Apr 24, 2024
8ceb7f5
fix imports and remove logs
Ryukemeister Apr 24, 2024
46d8433
fixup
Ryukemeister Apr 24, 2024
6ef1c3e
update redirect url
Ryukemeister Apr 25, 2024
6bc4d0c
split oauth client form into separate update and create forms
Ryukemeister Apr 25, 2024
149b7a4
separate forms for create and edit oauth clients
Ryukemeister Apr 25, 2024
93e91ad
fixup
Ryukemeister Apr 25, 2024
e16c8d9
Merge branch 'main' into platform-onboarding
Ryukemeister Apr 25, 2024
4890edb
fixup
Ryukemeister Apr 25, 2024
f6f6d21
dynamic routes for oauth client edit page
Ryukemeister Apr 25, 2024
a2b8ae1
Merge branch 'main' into platform-onboarding
ThyMinimalDev Apr 25, 2024
25fc7b6
fixup fixup
Ryukemeister Apr 25, 2024
0bd4bfe
fix to not show error when redirect uri is empty
Ryukemeister Apr 25, 2024
f8dbab2
Merge remote-tracking branch 'origin/main' into platform-onboarding
exception Apr 30, 2024
70b7191
refactor create handler for org
Ryukemeister May 1, 2024
29f2571
cleaup comments
Ryukemeister May 1, 2024
f130e7c
Merge branch 'main' into platform-onboarding
CarinaWolli May 2, 2024
3e46d1e
add custom hook to check user billing
Ryukemeister May 2, 2024
ee570de
export managed user type
Ryukemeister May 2, 2024
64f1df5
refactor platform index page
Ryukemeister May 2, 2024
d67778e
refactor edit and create pages
Ryukemeister May 2, 2024
a20fba5
dashboard component containing oauth client list and managed user
Ryukemeister May 2, 2024
27e82ba
common oauth client form used for create and edit form
Ryukemeister May 2, 2024
618fbd4
platform pricing helper
Ryukemeister May 2, 2024
d46c923
platform pricing component
Ryukemeister May 2, 2024
f85960f
Merge branch 'main' into platform-onboarding
Ryukemeister May 2, 2024
8ea2d69
fix typing and data response for billing
Ryukemeister May 2, 2024
7b7b3af
use custom hook to check team billing info
Ryukemeister May 2, 2024
c4079a8
fix type checks
Ryukemeister May 2, 2024
1cc574a
upgrade conditional rendering for upgrade to org banner
Ryukemeister May 2, 2024
70191d1
add isLoading prop to check button loading state
Ryukemeister May 2, 2024
fa1cc9f
pass in button handler
Ryukemeister May 2, 2024
83e7651
add custom hook to subscribe to stripe and typings
Ryukemeister May 2, 2024
4b5be2d
update typings
Ryukemeister May 3, 2024
c88dc2c
fix incorrect endpoint
Ryukemeister May 3, 2024
65a0154
pass in team id as prop
Ryukemeister May 3, 2024
cfe929e
fix type check
Ryukemeister May 3, 2024
169889b
Merge branch 'main' into platform-onboarding
Ryukemeister May 3, 2024
ab670f1
update stripe success and cancel redirect url
Ryukemeister May 3, 2024
d840005
add and pass redirect url param to custom hook
Ryukemeister May 3, 2024
ba4d0c9
Merge branch 'main' into platform-onboarding
Ryukemeister May 3, 2024
33c2205
custom hooks for platform
Ryukemeister May 6, 2024
dfce9b9
cleanup
Ryukemeister May 6, 2024
e1c0c96
update imports
Ryukemeister May 6, 2024
bbcee0f
fix merge conflicts
Ryukemeister May 6, 2024
726281b
fixup
Ryukemeister May 6, 2024
5a1ef25
fixup fixup
Ryukemeister May 6, 2024
b27ad9e
fixup
Ryukemeister May 6, 2024
354c41e
merge conflicts fixup
Ryukemeister May 6, 2024
f64dce7
merge conlficts battle :(
Ryukemeister May 6, 2024
a430982
Merge branch 'main' into platform-onboarding
Ryukemeister May 6, 2024
5a36412
minor fixes
Ryukemeister May 6, 2024
1ed407c
skip admin checks for a platform client
Ryukemeister May 6, 2024
7a4f6a8
Merge branch 'main' into platform-onboarding
keithwillcode May 6, 2024
c98d361
Merge branch 'main' into platform-onboarding
Ryukemeister May 6, 2024
9da1748
Merge branch 'main' into platform-onboarding
PeerRich May 6, 2024
f6ce2b1
Merge branch 'main' into platform-onboarding
Ryukemeister May 7, 2024
903dad2
fix typo
Ryukemeister May 7, 2024
fff71d9
append slug with _platform for a platform user
Ryukemeister May 7, 2024
fcd9dfd
Merge branch 'main' into platform-onboarding
Ryukemeister May 7, 2024
bd4f5cd
Merge branch 'main' into platform-onboarding
PeerRich May 7, 2024
465d832
PR feedback
Ryukemeister May 8, 2024
6b4087c
dashboard refactor
Ryukemeister May 8, 2024
1a5bd54
bring back org form to its orginal state
Ryukemeister May 8, 2024
d8c0a59
add platform folder to ee
Ryukemeister May 8, 2024
5d835e1
update typings
Ryukemeister May 8, 2024
8a0a042
use new create platform form
Ryukemeister May 8, 2024
e65b0c1
fixup
Ryukemeister May 8, 2024
5f66058
fix typo and update plans
Ryukemeister May 8, 2024
6578b7f
Merge branch 'main' into platform-onboarding
Ryukemeister May 8, 2024
3a7dd7e
simplifying rendering
Ryukemeister May 8, 2024
d4939b0
Merge branch 'main' into platform-onboarding
ThyMinimalDev May 8, 2024
55a4801
remove managed users endpoint since it already exists
Ryukemeister May 8, 2024
ec8a1ec
url for endpoint
Ryukemeister May 8, 2024
a053092
Merge branch 'main' into platform-onboarding
Ryukemeister May 8, 2024
e7dfce8
rename tabs
Ryukemeister May 9, 2024
b4dc744
pr feedback
Ryukemeister May 9, 2024
d4a0bef
managed users endpoint
Ryukemeister May 9, 2024
1070ce9
update endpoint
Ryukemeister May 9, 2024
470212a
remove form
Ryukemeister May 9, 2024
e6325c8
Merge branch 'main' into platform-onboarding
Ryukemeister May 9, 2024
15f8f99
Merge branch 'main' into platform-onboarding
Ryukemeister May 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/api/v2/src/modules/billing/services/billing.service.ts
Expand Up @@ -62,8 +62,8 @@ export class BillingService {
quantity: 1,
},
],
success_url: `${this.webAppUrl}/settings/platform/oauth-clients`,
cancel_url: `${this.webAppUrl}/settings/platform/oauth-clients`,
success_url: `${this.webAppUrl}/settings/platform/`,
cancel_url: `${this.webAppUrl}/settings/platform/`,
mode: "subscription",
metadata: {
teamId: teamId.toString(),
Expand Down
1 change: 1 addition & 0 deletions apps/api/v2/src/modules/billing/types.ts
@@ -1,5 +1,6 @@
export enum PlatformPlan {
STARTER = "STARTER",
ESSENTIALS = "ESSENTIALS",
SCALE = "SCALE",
ENTERPRISE = "ENTERPRISE",
}
Expand Up @@ -3,13 +3,15 @@ import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Roles } from "@/modules/auth/decorators/roles/roles.decorator";
import { NextAuthGuard } from "@/modules/auth/guards/next-auth/next-auth.guard";
import { OrganizationRolesGuard } from "@/modules/auth/guards/organization-roles/organization-roles.guard";
import { ManagedUserOutput } from "@/modules/oauth-clients/controllers/oauth-client-users/outputs/managed-user.output";
import { CreateOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/CreateOAuthClientResponse.dto";
import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientResponse.dto";
import { GetOAuthClientsResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientsResponse.dto";
import { UpdateOAuthClientInput } from "@/modules/oauth-clients/inputs/update-oauth-client.input";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { OrganizationsRepository } from "@/modules/organizations/organizations.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import { UsersRepository } from "@/modules/users/users.repository";
import {
Body,
Controller,
Expand All @@ -32,6 +34,7 @@ import {
ApiCreatedResponse as DocsCreatedResponse,
} from "@nestjs/swagger";
import { MembershipRole } from "@prisma/client";
import { User } from "@prisma/client";

import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { CreateOAuthClientInput } from "@calcom/platform-types";
Expand All @@ -51,6 +54,7 @@ export class OAuthClientsController {

constructor(
private readonly oauthClientRepository: OAuthClientRepository,
private readonly userRepository: UsersRepository,
private readonly teamsRepository: OrganizationsRepository
) {}

Expand Down Expand Up @@ -131,4 +135,17 @@ export class OAuthClientsController {
const client = await this.oauthClientRepository.deleteOAuthClient(clientId);
return { status: SUCCESS_STATUS, data: client };
}

private getResponseUser(user: User): ManagedUserOutput {
return {
id: user.id,
email: user.email,
username: user.username,
timeZone: user.timeZone,
weekStart: user.weekStart,
createdDate: user.createdDate,
timeFormat: user.timeFormat,
defaultScheduleId: user.defaultScheduleId,
};
}
}
@@ -0,0 +1,33 @@
import type { PlatformOAuthClient } from "@calcom/prisma/client";

import { OAuthClientsDropdown } from "@components/settings/platform/dashboard/oauth-client-dropdown";

type ManagedUserHeaderProps = {
oauthClients: PlatformOAuthClient[];
initialClientName: string;
handleChange: (clientId: string, clientName: string) => void;
};

export const ManagedUserHeader = ({
oauthClients,
initialClientName,
handleChange,
}: ManagedUserHeaderProps) => {
return (
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold leading-5 tracking-wide">
Managed Users
</h1>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">
See all the managed users created by your OAuth client.
</p>
</div>
<OAuthClientsDropdown
oauthClients={oauthClients}
initialClientName={initialClientName}
handleChange={handleChange}
/>
</div>
);
};
@@ -0,0 +1,39 @@
import type { PlatformOAuthClient } from "@calcom/prisma/client";

import type { ManagedUser } from "@lib/hooks/settings/platform/oauth-clients/useOAuthClients";

import { ManagedUserHeader } from "@components/settings/platform/dashboard/managed-user-header";
import { ManagedUserTable } from "@components/settings/platform/dashboard/managed-user-table";

type ManagedUserListProps = {
oauthClients: PlatformOAuthClient[];
managedUsers?: ManagedUser[];
initialClientName: string;
initialClientId: string;
isManagedUserLoading: boolean;
handleChange: (clientId: string, clientName: string) => void;
};

export const ManagedUserList = ({
initialClientName,
initialClientId,
oauthClients,
managedUsers,
isManagedUserLoading,
handleChange,
}: ManagedUserListProps) => {
return (
<div>
<ManagedUserHeader
oauthClients={oauthClients}
initialClientName={initialClientName}
handleChange={handleChange}
/>
<ManagedUserTable
managedUsers={managedUsers}
isManagedUserLoading={isManagedUserLoading}
initialClientId={initialClientId}
/>
</div>
);
};
@@ -0,0 +1,60 @@
import { EmptyScreen } from "@calcom/ui";

import type { ManagedUser } from "@lib/hooks/settings/platform/oauth-clients/useOAuthClients";

type ManagedUserTableProps = {
managedUsers?: ManagedUser[];
isManagedUserLoading: boolean;
initialClientId: string;
};

export const ManagedUserTable = ({
managedUsers,
isManagedUserLoading,
initialClientId,
}: ManagedUserTableProps) => {
const showUsers = !isManagedUserLoading && managedUsers?.length;

return (
<div>
{showUsers ? (
<>
<table className="w-[100%] rounded-lg">
<colgroup className="border-subtle overflow-hidden rounded-b-lg border border-b-0" span={3} />
<tr>
<td className="border-subtle border px-4 py-3 md:text-center">Id</td>
<td className="border-subtle border px-4 py-3 md:text-center">Username</td>
<td className="border-subtle border px-4 py-3 md:text-center">Email</td>
</tr>
{managedUsers.map((user) => {
return (
<tr key={user.id} className="">
<td className="border-subtle overflow-hidden border px-4 py-3 md:text-center">{user.id}</td>
<td className="border-subtle border px-4 py-3 md:text-center">{user.username}</td>
<td className="border-subtle overflow-hidden border px-4 py-3 md:overflow-auto md:text-center">
{user.email}
</td>
</tr>
);
})}
</table>
</>
) : (
<EmptyScreen
limitWidth={false}
headline={
initialClientId == undefined
? "OAuth client is missing. You need to create an OAuth client first in order to create a managed user."
: `OAuth client ${initialClientId} does not have a managed user present.`
}
description={
initialClientId == undefined
? "Refer to the Platform Docs from the sidebar in order to create an OAuth client."
: "Refer to the Platform Docs from the sidebar in order to create a managed user."
}
className="items-center border"
/>
)}
</div>
);
};
@@ -0,0 +1,50 @@
import type { PlatformOAuthClient } from "@calcom/prisma/client";
import {
Button,
Dropdown,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownItem,
} from "@calcom/ui";

type OAuthClientsDropdownProps = {
oauthClients: PlatformOAuthClient[];
initialClientName: string;
handleChange: (clientId: string, clientName: string) => void;
};

export const OAuthClientsDropdown = ({
oauthClients,
initialClientName,
handleChange,
}: OAuthClientsDropdownProps) => {
return (
<div>
{Array.isArray(oauthClients) && oauthClients.length > 0 ? (
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button color="secondary">{initialClientName}</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
{oauthClients.map((client) => {
return (
<div key={client.id}>
{initialClientName !== client.name ? (
<DropdownMenuItem className="outline-none">
<DropdownItem type="button" onClick={() => handleChange(client.id, client.name)}>
{client.name}
</DropdownItem>
</DropdownMenuItem>
) : (
<></>
)}
</div>
);
})}
</DropdownMenuContent>
</Dropdown>
) : null}
</div>
);
};
@@ -0,0 +1,81 @@
import { useRouter } from "next/navigation";

import type { PlatformOAuthClient } from "@calcom/prisma/client";
import { EmptyScreen, Button } from "@calcom/ui";

import { OAuthClientCard } from "@components/settings/platform/oauth-clients/OAuthClientCard";

type OAuthClientsListProps = {
oauthClients: PlatformOAuthClient[];
isDeleting: boolean;
handleDelete: (id: string) => Promise<void>;
};

export const OAuthClientsList = ({ oauthClients, isDeleting, handleDelete }: OAuthClientsListProps) => {
return (
<div className="mb-10">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
<h1 className="font-cal text-emphasis mb-1 text-xl font-semibold leading-5 tracking-wide">
OAuth Clients
</h1>
<p className="text-default text-sm ltr:mr-4 rtl:ml-4">
Connect your platform to cal.com with OAuth
</p>
</div>
<div>
<NewOAuthClientButton redirectLink="/settings/platform/oauth-clients/create" />
</div>
</div>
{Array.isArray(oauthClients) && oauthClients.length ? (
<>
<div className="border-subtle rounded-b-lg border border-t-0">
{oauthClients.map((client, index) => {
return (
<OAuthClientCard
name={client.name}
redirectUris={client.redirectUris}
bookingRedirectUri={client.bookingRedirectUri}
bookingRescheduleRedirectUri={client.bookingRescheduleRedirectUri}
bookingCancelRedirectUri={client.bookingCancelRedirectUri}
permissions={client.permissions}
key={index}
lastItem={oauthClients.length === index + 1}
id={client.id}
secret={client.secret}
isLoading={isDeleting}
onDelete={handleDelete}
areEmailsEnabled={client.areEmailsEnabled}
/>
);
})}
</div>
</>
) : (
<EmptyScreen
headline="Create your first OAuth client"
description="OAuth clients facilitate access to Cal.com on behalf of users"
Icon="plus"
className=""
buttonRaw={<NewOAuthClientButton redirectLink="/settings/platform/oauth-clients/create" />}
/>
)}
</div>
);
};

const NewOAuthClientButton = ({ redirectLink, label }: { redirectLink: string; label?: string }) => {
const router = useRouter();

return (
<Button
onClick={(e) => {
e.preventDefault();
router.push(redirectLink);
}}
color="secondary"
StartIcon="plus">
{!!label ? label : "Add"}
</Button>
);
};
Expand Up @@ -6,7 +6,7 @@ import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants";
import type { Avatar } from "@calcom/prisma/client";
import { Button, Icon, showToast } from "@calcom/ui";

import { hasPermission } from "../../../../../../../packages/platform/utils/permissions";
import { hasPermission } from "../../../../../../packages/platform/utils/permissions";

type OAuthClientCardProps = {
name: string;
Expand Down Expand Up @@ -115,7 +115,7 @@ export const OAuthClientCard = ({
</div>
<div className="border-subtle flex text-sm">
<span className="font-semibold">Permissions: </span>
<div className="flex">{clientPermissions}</div>
{permissions ? <div className="flex">{clientPermissions}</div> : <>&nbsp;Disabled</>}
</div>
<div className="flex gap-1 text-sm">
<span className="font-semibold">Redirect uris: </span>
Expand Down Expand Up @@ -145,7 +145,7 @@ export const OAuthClientCard = ({
className="bg-subtle hover:bg-emphasis text-white"
loading={isLoading}
disabled={isLoading}
onClick={() => router.push(`/settings/organizations/platform/oauth-clients/create?clientId=${id}`)}>
onClick={() => router.push(`/settings/platform/oauth-clients/${id}/edit`)}>
Edit
</Button>
<Button
Expand Down