-
Notifications
You must be signed in to change notification settings - Fork 8.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: platform oAuth clients frontend (#12867)
* add oauth client to settings * fix imports * add react and axios * oauth client form and card components * hooks for oauth clients data * index page for oauth clients * oauth client list component * oauth client form page * shift atoms into platform * init platform folder * refactor handleSubmit functioin * platform * platform * platform parts * revert tsconfig constant platform * fix: useOauthClients * feat: create oauth client with api * fix: add prettier to platform type package * fixup! fix: add prettier to platform type package * chore: class-validator types in platform package * add types for delete oauth client iput * add onSuccess and onError methods * update oauth client card view with client id and secret * cleanup comments * split oauth persisit hook into create and delete hooks * fix: oauth client creation / deletion / listing * fixup! fix: oauth client creation / deletion / listing * fix: comment logo for now * fix: layout setting org keys * cleanup comments * minor style fixes, add logic for client permissions * show toast after deleting client * not passing clint logo at the moment --------- Co-authored-by: Morgan Vernay <[email protected]>
- Loading branch information
1 parent
64e1658
commit b987f6e
Showing
22 changed files
with
693 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
// import { useOAuthClient } from "@pages/settings/platform/oauth-clients/hooks/useOAuthClients"; | ||
import { useRouter } from "next/navigation"; | ||
|
||
import { APP_NAME } from "@calcom/lib/constants"; | ||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams"; | ||
import { useLocale } from "@calcom/lib/hooks/useLocale"; | ||
import { Avatar, Button } from "@calcom/ui"; | ||
import { Info } from "@calcom/ui/components/icon"; | ||
import { Plus } from "@calcom/ui/components/icon"; | ||
|
||
import PageWrapper from "@components/PageWrapper"; | ||
|
||
import { PERMISSIONS_GROUPED_MAP } from "../../../../../packages/platform/constants/permissions"; | ||
import { hasPermission } from "../../../../../packages/platform/utils/permissions"; | ||
|
||
export default function Authorize() { | ||
const { t } = useLocale(); | ||
const router = useRouter(); | ||
|
||
const searchParams = useCompatSearchParams(); | ||
const queryString = searchParams?.toString(); | ||
|
||
// const { isLoading, error, data: client } = useOAuthClient(queryString); | ||
|
||
const client: { | ||
name: string; | ||
logo?: string; | ||
redirect_uris: string[]; | ||
permissions: number; | ||
} = { | ||
name: "Acme.com", | ||
redirect_uris: ["", ""], | ||
permissions: 7, | ||
}; | ||
|
||
console.log("These are the search params:", queryString); | ||
|
||
const permissions = Object.values(PERMISSIONS_GROUPED_MAP).map((value) => { | ||
let permissionsMessage = ""; | ||
const hasReadPermission = hasPermission(client.permissions, value.read); | ||
const hasWritePermission = hasPermission(client.permissions, value.write); | ||
|
||
if (hasReadPermission || hasWritePermission) { | ||
permissionsMessage = hasReadPermission ? "Read" : "Write"; | ||
} | ||
|
||
if (hasReadPermission && hasWritePermission) { | ||
permissionsMessage = "Read, write"; | ||
} | ||
|
||
return ( | ||
!!permissionsMessage && ( | ||
<li key={value.read} className="relative pl-5 text-sm"> | ||
<span className="absolute left-0">✓</span> | ||
{permissionsMessage} your {`${value.label}s`.toLocaleLowerCase()} | ||
</li> | ||
) | ||
); | ||
}); | ||
|
||
return ( | ||
<div className="flex min-h-screen items-center justify-center"> | ||
<div className="mt-2 max-w-xl rounded-md bg-white px-9 pb-3 pt-2"> | ||
<div className="flex items-center justify-center"> | ||
{/* | ||
below is where the client logo will be displayed | ||
first we check if the client has a logo property and display logo if present | ||
else we take logo from user profile pic | ||
*/} | ||
{client.logo ? ( | ||
<Avatar | ||
alt="" | ||
fallback={<Plus className="text-subtle h-6 w-6" />} | ||
className="items-center" | ||
imageSrc={client.logo} | ||
size="lg" | ||
/> | ||
) : ( | ||
<Avatar | ||
alt="" | ||
fallback={<Plus className="text-subtle h-6 w-6" />} | ||
className="items-center" | ||
imageSrc="/cal-com-icon.svg" | ||
size="lg" | ||
/> | ||
)} | ||
<div className="relative -ml-6 h-24 w-24"> | ||
<div className="absolute inset-0 flex items-center justify-center"> | ||
<div className="flex h-[70px] w-[70px] items-center justify-center rounded-full bg-white"> | ||
<img src="/cal-com-icon.svg" alt="Logo" className="h-16 w-16 rounded-full" /> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
<h1 className="px-5 pb-5 pt-3 text-center text-2xl font-bold tracking-tight text-black"> | ||
{t("access_cal_account", { clientName: client.name, appName: APP_NAME })} | ||
</h1> | ||
<div className="mb-4 mt-5 font-medium text-black"> | ||
{t("allow_client_to", { clientName: client.name })} | ||
</div> | ||
<ul className="space-y-4 text-sm text-black">{permissions}</ul> | ||
<div className="bg-subtle mb-8 mt-8 flex rounded-md p-3"> | ||
<div> | ||
<Info className="mr-1 mt-0.5 h-4 w-4" /> | ||
</div> | ||
<div className="ml-1 "> | ||
<div className="mb-1 text-sm font-medium"> | ||
{t("allow_client_to_do", { clientName: client.name })} | ||
</div> | ||
<div className="text-sm">{t("oauth_access_information", { appName: APP_NAME })}</div>{" "} | ||
</div> | ||
</div> | ||
<div className="border-subtle border- -mx-9 mb-4 border-b" /> | ||
<div className="flex justify-end"> | ||
<Button | ||
className="bg-primary mr-2 text-black" | ||
onClick={() => { | ||
router.back(); | ||
}}> | ||
{t("go_back")} | ||
</Button> | ||
<Button data-testid="allow-button" className="bg-black text-white"> | ||
{t("allow")} | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
Authorize.PageWrapper = PageWrapper; |
124 changes: 124 additions & 0 deletions
124
apps/web/pages/settings/organizations/platform/oauth-clients/components/OAuthClientCard.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
import { Asterisk, Clipboard } from "lucide-react"; | ||
import React from "react"; | ||
|
||
import { classNames } from "@calcom/lib"; | ||
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants"; | ||
import type { Avatar } from "@calcom/prisma/client"; | ||
import { Button, showToast } from "@calcom/ui"; | ||
|
||
import { hasPermission } from "../../../../../../../../packages/platform/utils/permissions"; | ||
|
||
type OAuthClientCardProps = { | ||
name: string; | ||
logo?: Avatar; | ||
redirect_uris: string[]; | ||
permissions: number; | ||
lastItem: boolean; | ||
id: string; | ||
secret: string; | ||
onDelete: (id: string) => Promise<void>; | ||
isLoading: boolean; | ||
}; | ||
|
||
export const OAuthClientCard = ({ | ||
name, | ||
logo, | ||
redirect_uris, | ||
permissions, | ||
id, | ||
secret, | ||
lastItem, | ||
onDelete, | ||
isLoading, | ||
}: OAuthClientCardProps) => { | ||
const clientPermissions = Object.values(PERMISSIONS_GROUPED_MAP).map((value, index) => { | ||
let permissionsMessage = ""; | ||
const hasReadPermission = hasPermission(permissions, value.read); | ||
const hasWritePermission = hasPermission(permissions, value.write); | ||
|
||
if (hasReadPermission || hasWritePermission) { | ||
permissionsMessage = hasReadPermission ? "read" : "write"; | ||
} | ||
|
||
if (hasReadPermission && hasWritePermission) { | ||
permissionsMessage = "read/write"; | ||
} | ||
|
||
return ( | ||
!!permissionsMessage && ( | ||
<div key={value.read} className="relative text-sm"> | ||
{permissionsMessage} {`${value.label}s`.toLocaleLowerCase()} | ||
{Object.values(PERMISSIONS_GROUPED_MAP).length === index + 1 ? " " : ", "} | ||
</div> | ||
) | ||
); | ||
}); | ||
|
||
return ( | ||
<div | ||
className={classNames( | ||
"flex w-full justify-between px-4 py-4 sm:px-6", | ||
lastItem ? "" : "border-subtle border-b" | ||
)}> | ||
<div className="flex flex-col gap-2"> | ||
<div className="flex gap-1"> | ||
<p className="font-semibold"> | ||
Client name: <span className="font-normal">{name}</span> | ||
</p> | ||
</div> | ||
{!!logo && ( | ||
<div> | ||
<>{logo}</> | ||
</div> | ||
)} | ||
<div className="flex flex-col gap-2"> | ||
<div className="flex flex-row items-center gap-2"> | ||
<div className="font-semibold">Client Id:</div> | ||
<div>{id}</div> | ||
<Clipboard | ||
type="button" | ||
className="h-4 w-4 cursor-pointer" | ||
onClick={() => { | ||
navigator.clipboard.writeText(id); | ||
showToast("Client id copied to clipboard.", "success"); | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
<div className="flex items-center gap-2"> | ||
<div className="font-semibold">Client Secret:</div> | ||
<div className="flex items-center justify-center rounded-md"> | ||
{[...new Array(20)].map((_, index) => ( | ||
<Asterisk key={`${index}asterisk`} className="h-2 w-2" /> | ||
))} | ||
<Clipboard | ||
type="button" | ||
className="ml-2 h-4 w-4 cursor-pointer" | ||
onClick={() => { | ||
navigator.clipboard.writeText(secret); | ||
showToast("Client secret copied to clipboard.", "success"); | ||
}} | ||
/> | ||
</div> | ||
</div> | ||
<div className="border-subtle flex text-sm"> | ||
<span className="font-semibold">Permissions: </span> | ||
<div className="flex">{clientPermissions}</div> | ||
</div> | ||
<div className="flex gap-1 text-sm"> | ||
<span className="font-semibold">Redirect uris: </span> | ||
{redirect_uris.map((item, index) => (redirect_uris.length === index + 1 ? `${item}` : `${item}, `))} | ||
</div> | ||
</div> | ||
<div className="flex items-center"> | ||
<Button | ||
className="bg-red-500 text-white hover:bg-red-600" | ||
loading={isLoading} | ||
disabled={isLoading} | ||
onClick={() => onDelete(id)}> | ||
Delete | ||
</Button> | ||
</div> | ||
</div> | ||
); | ||
}; |
Oops, something went wrong.