Skip to content

Commit

Permalink
new route
Browse files Browse the repository at this point in the history
  • Loading branch information
hariombalhara committed Sep 15, 2024
1 parent cf74351 commit 18a144d
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 9 deletions.
12 changes: 10 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
- TODO
- Google Meet App to handle Domain wide delegation
- Disable multiple installations of Google Calendar app if Domain wide delegation is enabled(If Domain wide delegation is enabled, should we disable multiple installations of Google Calendar app?)
- [x] Google Meet App to handle Domain wide delegation - Nothing needed here
- [x] Disable multiple installations of Google Calendar app if Domain wide delegation is enabled
- [x] Disabled it for now. In future we should allow it.
- [ ] Where should we show the user the client ID to enable domain wide delegation?
- [ ] It must be shown to the organization owner/admin only
- [ ] There could be multiple checkboxes per domain to enable domain wide delegation for a domain
- [ ] Support multiple domains in DomainWideDelegation schema for an organization
- [ ] Use the domain as well to identify if the domain wide delegation is enabled
- [ ] Automatically mark a DomainWideDelegation to have status as "error" if there is an error during event creation using it. Note that we might not have a fallback to go to but we could inform the admin about it.

24 changes: 24 additions & 0 deletions apps/web/pages/settings/organizations/domain-wide-delegation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import DomainWideDelegationList from "@calcom/features/ee/organizations/pages/settings/domainWideDelegation/domainWideDelegationList";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Meta } from "@calcom/ui";

import PageWrapper from "@components/PageWrapper";

const Page = () => {
const { t } = useLocale();
return (
<>
<Meta
borderInShellHeader={false}
title={t("domain_wide_delegation")}
description={t("domain_wide_delegation_description")}
/>
<DomainWideDelegationList />
</>
);
};

Page.getLayout = getLayout;
Page.PageWrapper = PageWrapper;
export default Page;
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import { useState } from "react";
import { useForm, Controller } from "react-hook-form";

import { useLocale } from "@calcom/lib/hooks/useLocale";
import {
Dropdown,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
Button,
Dialog,
DialogContent,
DialogFooter,
Form,
TextField,
SelectField,
} from "@calcom/ui";

interface DelegationItemProps {
delegation: {
id: number;
domain: string;
enabled: boolean;
workspacePlatform: {
name: string;
slug: string;
};
clientId: string;
};
toggleDelegation: (id: number) => void;
onEdit: (delegation: DelegationItemProps["delegation"]) => void;
}

function DelegationListItemActions({
delegation,
toggleDelegation,
onEdit,
}: {
delegation: DelegationItemProps["delegation"];
toggleDelegation: (id: number) => void;
onEdit: (delegation: DelegationItemProps["delegation"]) => void;
}) {
const { t } = useLocale();

return (
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
type="button"
variant="icon"
color="secondary"
StartIcon="ellipsis"
className="ltr:radix-state-open:rounded-r-md rtl:radix-state-open:rounded-l-md"
/>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
<Button
type="button"
color="minimal"
StartIcon={delegation.enabled ? "check" : "x"}
onClick={() => toggleDelegation(delegation.id)}>
{delegation.enabled ? t("disable") : t("enable")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem>
<Button type="button" color="minimal" StartIcon="pencil" onClick={() => onEdit(delegation)}>
{t("edit")}
</Button>
</DropdownMenuItem>
{/* Add more menu items as needed */}
</DropdownMenuContent>
</Dropdown>
);
}

function DelegationListItem({ delegation, toggleDelegation, onEdit }: DelegationItemProps) {
const { t } = useLocale();

return (
<li className="border-subtle bg-default divide-subtle flex flex-col divide-y rounded-lg border">
<div className="flex items-center justify-between space-x-2 p-4">
<div className="flex items-center space-x-2">
<span className="mr-2">{delegation.domain}</span>
<span>{delegation.workspacePlatform.name}</span>
</div>
<DelegationListItemActions
delegation={delegation}
toggleDelegation={toggleDelegation}
onEdit={onEdit}
/>
</div>
</li>
);
}

function CreateEditDelegationDialog({
isOpen,
onClose,
delegation,
onSubmit,
}: {
isOpen: boolean;
onClose: () => void;
delegation: DelegationItemProps["delegation"] | null;
onSubmit: (data: { domain: string; clientId: string; workspacePlatform: string }) => void;
}) {
const { t } = useLocale();
const form = useForm<{ domain: string; clientId: string; workspacePlatform: string }>({
defaultValues: {
domain: delegation?.domain || "",
clientId: delegation?.clientId || "",
workspacePlatform: delegation?.workspacePlatform.slug || "",
},
});

// TODO: Use tRPC route
const workspacePlatformOptions = [
{ value: "google", label: "Google" },
{ value: "microsoft", label: "Microsoft" },
];

return (
<Dialog open={isOpen} onOpenChange={onClose}>
<DialogContent>
<Form form={form} handleSubmit={onSubmit}>
<TextField label={t("domain")} {...form.register("domain")} />
<Controller
name="workspacePlatform"
control={form.control}
render={({ field: { value, onChange } }) => (
<SelectField
label={t("workspace_platform")}
onChange={(option) => onChange(option?.value)}
value={workspacePlatformOptions.find((opt) => opt.value === value)}
options={workspacePlatformOptions}
/>
)}
/>
<DialogFooter>
<Button type="button" color="secondary" onClick={onClose}>
{t("cancel")}
</Button>
<Button type="submit">{delegation ? t("save") : t("create")}</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
);
}

export default function DomainWideDelegationList() {
const { t } = useLocale();
// TODO: Use tRPC route
const [delegations, setDelegations] = useState([
{
id: 1,
domain: "example.com",
enabled: true,
workspacePlatform: {
name: "Google",
slug: "google",
},
clientId: "123",
},
]);

const toggleDelegation = (id: number) => {
// TODO: Use tRPC route
setDelegations((prevDelegations) =>
prevDelegations.map((delegation) =>
delegation.id === id ? { ...delegation, enabled: !delegation.enabled } : delegation
)
);
};

const updateDelegation = (id: number, data: { domain: string; workspacePlatform: string }) => {
// TODO: Use tRPC route
setDelegations((prevDelegations) =>
prevDelegations.map((delegation) =>
delegation.id === id
? {
...delegation,
domain: data.domain,
workspacePlatform: {
name: data.workspacePlatform === "google" ? "Google" : "Microsoft",
slug: data.workspacePlatform,
},
}
: delegation
)
);
};

const createDelegation = (data: { domain: string; workspacePlatform: string; clientId: string }) => {
const newDelegation = {
id: Date.now(), // Use a proper ID generation method in production
domain: data.domain,
clientId: data.clientId,
enabled: true,
workspacePlatform: {
name: data.workspacePlatform === "google" ? "Google" : "Microsoft",
slug: data.workspacePlatform,
},
};
setDelegations((prevDelegations) => [...prevDelegations, newDelegation]);
};

const [createEditDialog, setCreateEditDialog] = useState<{
isOpen: boolean;
delegation: DelegationItemProps["delegation"] | null;
}>({
isOpen: false,
delegation: null,
});

const onEditClick = (delegation: DelegationItemProps["delegation"]) => {
setCreateEditDialog({ isOpen: true, delegation });
};

const onCreateClick = () => setCreateEditDialog({ isOpen: true, delegation: null });

const handleSubmit = (data: { domain: string; workspacePlatform: string }) => {
if (createEditDialog.delegation) {
updateDelegation(createEditDialog.delegation.id, data);
} else {
createDelegation(data);
}
setCreateEditDialog({ isOpen: false, delegation: null });
};

return (
<div>
<ul>
{delegations.map((delegation) => (
<DelegationListItem
key={delegation.id}
delegation={delegation}
toggleDelegation={toggleDelegation}
onEdit={onEditClick}
/>
))}
</ul>
<CreateEditDelegationDialog
isOpen={createEditDialog.isOpen}
onClose={() => setCreateEditDialog({ isOpen: false, delegation: null })}
delegation={createEditDialog.delegation}
onSubmit={handleSubmit}
/>
<Button type="button" color="secondary" StartIcon="plus" className="mt-6" onClick={onCreateClick}>
{t("add_domain_wide_delegation")}
</Button>
</div>
);
}
4 changes: 4 additions & 0 deletions packages/features/settings/layouts/SettingsLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ const tabs: VerticalTabItemProps[] = [
name: "admin_api",
href: "https://cal.com/docs/enterprise-features/api/api-reference/bookings#admin-access",
},
{
name: "domain_wide_delegation",
href: "/settings/organizations/domain-wide-delegation",
},
],
},
{
Expand Down
44 changes: 37 additions & 7 deletions packages/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -1513,19 +1513,49 @@ model AttributeToUser {
@@unique([memberId, attributeOptionId])
}

enum WorkspacePlatform {
GOOGLE @map("google")
MICROSOFT @map("microsoft")
}

model DomainWideDelegation {
id String @id @default(uuid())
workspacePlatform WorkspacePlatform
workspacePlatform WorkspacePlatform @relation(fields: [workspacePlatformId], references: [id], onDelete: Cascade)
workspacePlatformId Int
// Provides possibility to have different service accounts for different organizations if the need arises, but normally they should be the same
serviceAccountKey Json
serviceAccountClientId String
enabled Boolean @default(false)
organizationId Int
organization Team @relation(fields: [organizationId], references: [id], onDelete: Cascade)
Credential Credential[]
domain String
inErrorState Boolean @default(false)
@@unique([organizationId, workspacePlatform])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Should be fare to assume that one domain can be only on one workspace platform at a time. So, one can't have two different workspace platforms for the same domain
@@unique([organizationId, domain])
// Ensures that two different organizations don't control the same domain
@@unique([domain])
}


// It is for domain-wide delegation
model WorkspacePlatform {
id Int @id @default(autoincrement())
slug String
name String
description String
defaultServiceAccountKey Json
defaultServiceAccountClientId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
enabled Boolean @default(false)
domainWideDelegations DomainWideDelegation[]
@@unique([slug])
}

0 comments on commit 18a144d

Please sign in to comment.