From 5fd8b1726fa973ae4b2bdeee6241e178f26ae8c9 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 29 Nov 2024 09:25:14 +0530 Subject: [PATCH 1/4] feat: add page to delete service account --- .../components/organization/api-keys/add.tsx | 2 +- .../organization/api-keys/columns.tsx | 30 +++-- .../organization/api-keys/delete.tsx | 104 ++++++++++++++++++ .../organization/api-keys/index.tsx | 6 +- .../react/components/organization/profile.tsx | 1 + .../react/components/organization/routes.tsx | 12 +- 6 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 sdks/js/packages/core/react/components/organization/api-keys/delete.tsx diff --git a/sdks/js/packages/core/react/components/organization/api-keys/add.tsx b/sdks/js/packages/core/react/components/organization/api-keys/add.tsx index 26975208b..c7a40d6e9 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/add.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/add.tsx @@ -70,7 +70,7 @@ export const AddServiceAccount = () => { } }); } - } catch ({ error }: any) { + } catch (error: any) { toast.error('Something went wrong', { description: error.message }); diff --git a/sdks/js/packages/core/react/components/organization/api-keys/columns.tsx b/sdks/js/packages/core/react/components/organization/api-keys/columns.tsx index 716fa34ab..cdf398240 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/columns.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/columns.tsx @@ -1,7 +1,7 @@ import { TrashIcon } from '@radix-ui/react-icons'; import { ApsaraColumnDef } from '@raystack/apsara'; import { Button, Flex, Text } from '@raystack/apsara/v1'; -import { Link } from '@tanstack/react-router'; +import { Link, useNavigate } from '@tanstack/react-router'; import dayjs from 'dayjs'; import { V1Beta1ServiceUser } from '~/api-client'; @@ -53,16 +53,26 @@ export const getColumns = ({ } }, cell: ({ row, getValue }) => { - return ( - - ); + return ; } } ]; }; + +function ServiceAccountDeleteAction({ id }: { id: string }) { + const navigate = useNavigate({ from: '/api-keys' }); + + function onDeleteClick() { + return navigate({ to: '/api-keys/$id/delete', params: { id: id } }); + } + return ( + + ); +} diff --git a/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx b/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx new file mode 100644 index 000000000..128e27708 --- /dev/null +++ b/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx @@ -0,0 +1,104 @@ +import { Dialog, Separator, Image } from '@raystack/apsara'; +import styles from './styles.module.css'; +import { Button, Flex, Text, toast } from '@raystack/apsara/v1'; +import cross from '~/react/assets/cross.svg'; +import { useNavigate, useParams } from '@tanstack/react-router'; +import { useFrontier } from '~/react/contexts/FrontierContext'; +import { useState } from 'react'; + +export const DeleteServiceAccount = () => { + const { id } = useParams({ from: '/api-keys/$id/delete' }); + const navigate = useNavigate({ from: '/api-keys/$id/delete' }); + const { client, activeOrganization: organization } = useFrontier(); + const [isLoading, setIsLoading] = useState(false); + + const orgId = organization?.id; + + async function onDeleteClick() { + try { + setIsLoading(true); + await client?.frontierServiceDeleteServiceUser(id, { org_id: orgId }); + navigate({ + to: '/api-keys', + state: { + refetch: true + } + }); + toast.success('Service account deleted'); + } catch (err: any) { + toast.error('Unable to delete service account', { + description: err?.message + }); + } finally { + setIsLoading(true); + } + } + + function onCancel() { + navigate({ to: '/api-keys' }); + } + + return ( + + {/* @ts-ignore */} + + + + Delete Service Account + + + cross navigate({ to: '/api-keys' })} + data-test-id="frontier-sdk-delete-service-account-close-btn" + /> + + + + + + This is an irreversible and permanent action doing this might result + in deletion of the service account and the keys associated with it. + Do you wish to proceed? + + + + + + + + + + ); +}; diff --git a/sdks/js/packages/core/react/components/organization/api-keys/index.tsx b/sdks/js/packages/core/react/components/organization/api-keys/index.tsx index 147c71bfe..d4128d7fb 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/index.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/index.tsx @@ -15,7 +15,7 @@ import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; import Skeleton from 'react-loading-skeleton'; import { getColumns } from './columns'; import { V1Beta1ServiceUser } from '~/api-client/dist'; -import { Outlet, useNavigate } from '@tanstack/react-router'; +import { Outlet, useLocation, useNavigate } from '@tanstack/react-router'; const NoServiceAccounts = ({ config @@ -165,6 +165,8 @@ const ServiceAccountsTable = ({ export default function ApiKeys() { const [serviceUsers, setServiceUsers] = useState([]); const [isServiceUsersLoading, setIsServiceUsersLoading] = useState(false); + const location = useLocation(); + const refetch = location?.state?.refetch; const { activeOrganization: organization, @@ -196,7 +198,7 @@ export default function ApiKeys() { if (organization?.id && canUpdateWorkspace) { getServiceAccounts(organization?.id); } - }, [organization?.id, client, canUpdateWorkspace]); + }, [organization?.id, client, canUpdateWorkspace, refetch]); const isLoading = isActiveOrganizationLoading || diff --git a/sdks/js/packages/core/react/components/organization/profile.tsx b/sdks/js/packages/core/react/components/organization/profile.tsx index 6201117e2..f9e0ec72d 100644 --- a/sdks/js/packages/core/react/components/organization/profile.tsx +++ b/sdks/js/packages/core/react/components/organization/profile.tsx @@ -63,5 +63,6 @@ declare module '@tanstack/react-router' { interface HistoryState { token?: V1Beta1ServiceUserToken; + refetch?: boolean; } } diff --git a/sdks/js/packages/core/react/components/organization/routes.tsx b/sdks/js/packages/core/react/components/organization/routes.tsx index 8b10257cb..0f0b403f5 100644 --- a/sdks/js/packages/core/react/components/organization/routes.tsx +++ b/sdks/js/packages/core/react/components/organization/routes.tsx @@ -44,6 +44,7 @@ import MemberRemoveConfirm from './members/MemberRemoveConfirm'; import APIKeys from './api-keys'; import { AddServiceAccount } from './api-keys/add'; import ServiceUserPage from './api-keys/service-user'; +import { DeleteServiceAccount } from './api-keys/delete'; export interface CustomScreen { name: string; @@ -308,6 +309,12 @@ const addServiceAccountRoute = createRoute({ component: AddServiceAccount }); +const deleteServiceAccountRoute = createRoute({ + getParentRoute: () => apiKeysRoute, + path: '/$id/delete', + component: DeleteServiceAccount +}); + const serviceAccountRoute = createRoute({ getParentRoute: () => rootRoute, path: '/api-keys/$id', @@ -337,7 +344,10 @@ export function getRootTree({ customScreens = [] }: getRootTreeOptions) { billingRoute.addChildren([switchBillingCycleModalRoute]), plansRoute.addChildren([planDowngradeRoute]), tokensRoute, - apiKeysRoute.addChildren([addServiceAccountRoute]), + apiKeysRoute.addChildren([ + addServiceAccountRoute, + deleteServiceAccountRoute + ]), serviceAccountRoute, ...customScreens.map(cc => createRoute({ From d3af14f5bfb8c7c1166f1be4636c6b7fe26c415e Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 29 Nov 2024 09:38:32 +0530 Subject: [PATCH 2/4] feat: add page to revoke service account key --- .../api-keys/service-user/delete.tsx | 114 ++++++++++++++++++ .../api-keys/service-user/index.tsx | 35 ++++-- .../api-keys/service-user/styles.module.css | 24 ++++ .../react/components/organization/routes.tsx | 9 +- 4 files changed, 174 insertions(+), 8 deletions(-) create mode 100644 sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx diff --git a/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx b/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx new file mode 100644 index 000000000..6daa4fbc2 --- /dev/null +++ b/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx @@ -0,0 +1,114 @@ +import { Dialog, Separator, Image } from '@raystack/apsara'; +import styles from './styles.module.css'; +import { Button, Flex, Text, toast } from '@raystack/apsara/v1'; +import cross from '~/react/assets/cross.svg'; +import { useNavigate, useParams } from '@tanstack/react-router'; +import { useFrontier } from '~/react/contexts/FrontierContext'; +import { useState } from 'react'; +import { DEFAULT_API_PLATFORM_APP_NAME } from '~/react/utils/constants'; + +export const DeleteServiceAccountKey = () => { + const { id, tokenId } = useParams({ + from: '/api-keys/$id/key/$tokenId/delete' + }); + const navigate = useNavigate({ from: '/api-keys/$id/key/$tokenId/delete' }); + const { client, config } = useFrontier(); + const [isLoading, setIsLoading] = useState(false); + + async function onDeleteClick() { + try { + setIsLoading(true); + await client?.frontierServiceDeleteServiceUserToken(id, tokenId); + navigate({ + to: '/api-keys/$id', + params: { + id: id + }, + state: { + refetch: true + } + }); + toast.success('Service account key revoked'); + } catch (err: any) { + toast.error('Unable to revoke service account key', { + description: err?.message + }); + } finally { + setIsLoading(true); + } + } + + function onCancel() { + navigate({ + to: '/api-keys/$id', + params: { + id: id + } + }); + } + const appName = config?.apiPlatform?.appName || DEFAULT_API_PLATFORM_APP_NAME; + + return ( + + {/* @ts-ignore */} + + + + Revoke API Key + + + cross + + + + + + This is an irreversible action doing this might lead to + discontinuation of access to the {appName} features. Do you wish to + proceed? + + + + + + + + + + ); +}; diff --git a/sdks/js/packages/core/react/components/organization/api-keys/service-user/index.tsx b/sdks/js/packages/core/react/components/organization/api-keys/service-user/index.tsx index bf2e464ee..33a61e79a 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/service-user/index.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/service-user/index.tsx @@ -48,13 +48,25 @@ const Headings = ({ const ServiceUserTokenItem = ({ token, - isLoading + isLoading, + serviceUserId }: { token: V1Beta1ServiceUserToken; isLoading: boolean; + serviceUserId: string; }) => { const { copy } = useCopyToClipboard(); + const navigate = useNavigate({ from: '/api-keys/$id' }); + function onRevokeClick() { + navigate({ + to: '/api-keys/$id/key/$tokenId/delete', + params: { + tokenId: token?.id || '', + id: serviceUserId + } + }); + } return ( @@ -72,6 +84,7 @@ const ServiceUserTokenItem = ({ variant="secondary" size={'small'} data-test-id={`frontier-sdk-service-account-token-revoke-btn`} + onClick={onRevokeClick} > Revoke @@ -102,10 +115,12 @@ const ServiceUserTokenItem = ({ const SerivceUserTokenList = ({ isLoading, - tokens + tokens, + serviceUserId }: { isLoading: boolean; tokens: V1Beta1ServiceUserToken[]; + serviceUserId: string; }) => { const tokenList = isLoading ? [ @@ -122,6 +137,7 @@ const SerivceUserTokenList = ({ token={token} key={token?.id} isLoading={isLoading} + serviceUserId={serviceUserId} /> ))} @@ -133,9 +149,6 @@ export default function ServiceUserPage() { const { client, config } = useFrontier(); const navigate = useNavigate({ from: '/api-keys/$id' }); - const location = useLocation(); - const existingToken = location?.state?.token; - const [serviceUser, setServiceUser] = useState(); const [isServiceUserLoadning, setIsServiceUserLoading] = useState(false); @@ -146,6 +159,10 @@ export default function ServiceUserPage() { const [isServiceUserTokensLoading, setIsServiceUserTokensLoading] = useState(false); + const location = useLocation(); + const existingToken = location?.state?.token; + const refetch = location?.state?.refetch; + const getServiceUser = useCallback( async (serviceUserId: string) => { try { @@ -187,7 +204,7 @@ export default function ServiceUserPage() { getServiceUserTokens(id); } } - }, [id, getServiceUser, getServiceUserTokens, existingToken?.id]); + }, [id, getServiceUser, getServiceUserTokens, existingToken?.id, refetch]); const tokenList = existingToken ? [existingToken, ...serviceUserTokens] @@ -220,7 +237,11 @@ export default function ServiceUserPage() { config={config?.apiPlatform} /> - + diff --git a/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css b/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css index c3539452f..cd4904f25 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css +++ b/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css @@ -60,3 +60,27 @@ border: 1px solid var(--rs-color-border-base-primary); background: var(--rs-color-background-base-primary-hover); } + +.addDialogContent { + padding: 0; + max-width: 400px; + width: 100%; + z-index: 60; +} + +.overlay { + z-index: 55; + background-color: rgba(104, 112, 118, 0.5); +} + +.addDialogForm { + padding: var(--rs-space-5) var(--rs-space-7); +} + +.addDialogFormContent { + padding: var(--space-9) var(--space-7); +} + +.addDialogFormBtnWrapper { + padding: var(--rs-space-5); +} diff --git a/sdks/js/packages/core/react/components/organization/routes.tsx b/sdks/js/packages/core/react/components/organization/routes.tsx index 0f0b403f5..701ede8d0 100644 --- a/sdks/js/packages/core/react/components/organization/routes.tsx +++ b/sdks/js/packages/core/react/components/organization/routes.tsx @@ -45,6 +45,7 @@ import APIKeys from './api-keys'; import { AddServiceAccount } from './api-keys/add'; import ServiceUserPage from './api-keys/service-user'; import { DeleteServiceAccount } from './api-keys/delete'; +import { DeleteServiceAccountKey } from './api-keys/service-user/delete'; export interface CustomScreen { name: string; @@ -321,6 +322,12 @@ const serviceAccountRoute = createRoute({ component: ServiceUserPage }); +const deleteServiceAccountKeyRoute = createRoute({ + getParentRoute: () => serviceAccountRoute, + path: '/key/$tokenId/delete', + component: DeleteServiceAccountKey +}); + interface getRootTreeOptions { customScreens?: CustomScreen[]; } @@ -348,7 +355,7 @@ export function getRootTree({ customScreens = [] }: getRootTreeOptions) { addServiceAccountRoute, deleteServiceAccountRoute ]), - serviceAccountRoute, + serviceAccountRoute.addChildren([deleteServiceAccountKeyRoute]), ...customScreens.map(cc => createRoute({ path: cc.path, From abfc20e5674be56642ac0200708755424ef39a89 Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 29 Nov 2024 11:58:37 +0530 Subject: [PATCH 3/4] fix: delete loading state --- .../core/react/components/organization/api-keys/delete.tsx | 2 +- .../components/organization/api-keys/service-user/delete.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx b/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx index 128e27708..7da9e0ede 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/delete.tsx @@ -30,7 +30,7 @@ export const DeleteServiceAccount = () => { description: err?.message }); } finally { - setIsLoading(true); + setIsLoading(false); } } diff --git a/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx b/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx index 6daa4fbc2..25dd4a8a4 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx +++ b/sdks/js/packages/core/react/components/organization/api-keys/service-user/delete.tsx @@ -34,7 +34,7 @@ export const DeleteServiceAccountKey = () => { description: err?.message }); } finally { - setIsLoading(true); + setIsLoading(false); } } From 96de2d7a8e591a17fcc897e79e8c687eedd4b1bb Mon Sep 17 00:00:00 2001 From: Rishabh Date: Fri, 29 Nov 2024 12:00:03 +0530 Subject: [PATCH 4/4] chore: update css variables --- .../organization/api-keys/service-user/styles.module.css | 4 ++-- .../components/organization/api-keys/styles.module.css | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css b/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css index cd4904f25..86efe0229 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css +++ b/sdks/js/packages/core/react/components/organization/api-keys/service-user/styles.module.css @@ -9,7 +9,7 @@ .content { width: 100%; - padding: var(--space-9) var(--space-11); + padding: var(--rs-space-9) var(--rs-space-11); } .serviceKeyList { @@ -78,7 +78,7 @@ } .addDialogFormContent { - padding: var(--space-9) var(--space-7); + padding: var(--rs-space-9) var(--rs-space-7); } .addDialogFormBtnWrapper { diff --git a/sdks/js/packages/core/react/components/organization/api-keys/styles.module.css b/sdks/js/packages/core/react/components/organization/api-keys/styles.module.css index e7a6b75d3..51f30967c 100644 --- a/sdks/js/packages/core/react/components/organization/api-keys/styles.module.css +++ b/sdks/js/packages/core/react/components/organization/api-keys/styles.module.css @@ -5,11 +5,11 @@ .content { width: 100%; - padding: var(--space-9) var(--space-11); + padding: var(--rs-space-9) var(--rs-space-11); } .stateContent { - padding: var(--space-15) var(--space-11); + padding: var(--rs-space-15) var(--rs-space-11); } .flex1 { @@ -43,7 +43,7 @@ } .addDialogFormContent { - padding: var(--space-9) var(--space-7); + padding: var(--rs-space-9) var(--rs-space-7); } .addDialogFormBtnWrapper {