Skip to content

Commit

Permalink
feat(sdk): add create service accounts page (#820)
Browse files Browse the repository at this point in the history
* feat: add route for new service account

* feat: add create service user flow
  • Loading branch information
rsbh authored Nov 25, 2024
1 parent 9804b79 commit 2b502d0
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 5 deletions.
139 changes: 139 additions & 0 deletions sdks/js/packages/core/react/components/organization/api-keys/add.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import {
Dialog,
Separator,
Image,
InputField,
TextField
} 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 } from '@tanstack/react-router';
import { Controller, useForm } from 'react-hook-form';
import { useFrontier } from '~/react/contexts/FrontierContext';
import * as yup from 'yup';
import { yupResolver } from '@hookform/resolvers/yup';
import { useCallback } from 'react';

const serviceAccountSchema = yup
.object({
title: yup.string().required('Name is a required field')
})
.required();

type FormData = yup.InferType<typeof serviceAccountSchema>;

export const AddServiceAccount = () => {
const navigate = useNavigate({ from: '/api-keys/add' });
const { client, activeOrganization: organization } = useFrontier();

const {
control,
handleSubmit,
formState: { errors, isSubmitting }
} = useForm({
resolver: yupResolver(serviceAccountSchema)
});

const orgId = organization?.id;

const onSubmit = useCallback(
async (data: FormData) => {
if (!client) return;
if (!orgId) return;

try {
const {
data: { serviceuser }
} = await client.frontierServiceCreateServiceUser({
body: data,
org_id: orgId
});
toast.success('Service user created');

navigate({
to: '/api-keys/$id',
params: { id: serviceuser?.id ?? '' }
});
} catch ({ error }: any) {
toast.error('Something went wrong', {
description: error.message
});
}
},
[client, navigate, orgId]
);

const isDisabled = isSubmitting;

return (
<Dialog open={true}>
{/* @ts-ignore */}
<Dialog.Content
style={{}}
overlayClassname={styles.overlay}
className={styles.addDialogContent}
>
<form onSubmit={handleSubmit(onSubmit)}>
<Flex justify="between" className={styles.addDialogForm}>
<Text size={6} weight={500}>
New Service Account
</Text>

<Image
alt="cross"
style={{ cursor: 'pointer' }}
// @ts-ignore
src={cross}
onClick={() => navigate({ to: '/api-keys' })}
data-test-id="frontier-sdk-new-service-account-close-btn"
/>
</Flex>
<Separator />

<Flex
direction="column"
gap="medium"
className={styles.addDialogFormContent}
>
<Text>
Create a dedicated service account to facilitate secure API
interactions on behalf of the organization.
</Text>

<InputField label="Name">
<Controller
render={({ field }) => (
<TextField
{...field}
size="medium"
placeholder="Provide service account name"
/>
)}
name="title"
control={control}
/>
<Text size={1} variant="danger">
{errors.title && String(errors.title?.message)}
</Text>
</InputField>
</Flex>
<Separator />
<Flex justify="end" className={styles.addDialogFormBtnWrapper}>
<Button
variant="primary"
size="medium"
type="submit"
data-test-id="frontier-sdk-add-service-account-btn"
loading={isSubmitting}
disabled={isDisabled}
loaderText={'Creating...'}
>
Create
</Button>
</Flex>
</form>
</Dialog.Content>
</Dialog>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +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';

const NoServiceAccounts = ({
config
Expand Down Expand Up @@ -126,6 +127,8 @@ const ServiceAccountsTable = ({
}) => {
const columns = getColumns({ dateFormat: dateFormat || DEFAULT_DATE_FORMAT });

const navigate = useNavigate({ from: '/api-keys' });

return (
<DataTable data={serviceUsers} columns={columns} isLoading={isLoading}>
{/* TODO: add className props to DataTable.Toolbar in apsara */}
Expand All @@ -146,6 +149,7 @@ const ServiceAccountsTable = ({
<Button
variant="primary"
data-test-id="frontier-sdk-add-service-account-btn"
onClick={() => navigate({ to: '/api-keys/add' })}
>
Create
</Button>
Expand Down Expand Up @@ -197,7 +201,7 @@ export default function ApiKeys() {
isPermissionsFetching ||
isServiceUsersLoading;

const serviceAccountsCount: number = 1;
const serviceAccountsCount: number = serviceUsers?.length;

return (
<Flex direction="column" style={{ width: '100%' }}>
Expand All @@ -206,9 +210,7 @@ export default function ApiKeys() {
</Flex>
<Flex justify="center" align="center">
{canUpdateWorkspace || isLoading ? (
serviceAccountsCount === 0 ? (
<NoServiceAccounts />
) : (
serviceAccountsCount > 0 || isLoading ? (
<Flex className={styles.content} direction="column" gap="large">
<Headings isLoading={isLoading} config={config?.apiPlatform} />
<ServiceAccountsTable
Expand All @@ -217,11 +219,14 @@ export default function ApiKeys() {
dateFormat={config?.dateFormat}
/>
</Flex>
) : (
<NoServiceAccounts />
)
) : (
<NoAccess />
)}
</Flex>
<Outlet />
</Flex>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,27 @@
max-width: 360px;
width: 100%;
}

.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);
}
16 changes: 15 additions & 1 deletion sdks/js/packages/core/react/components/organization/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import Plans from './plans';
import ConfirmPlanChange from './plans/confirm-change';
import MemberRemoveConfirm from './members/MemberRemoveConfirm';
import APIKeys from './api-keys';
import { AddServiceAccount } from './api-keys/add';

export interface CustomScreen {
name: string;
Expand Down Expand Up @@ -300,6 +301,18 @@ const apiKeysRoute = createRoute({
component: APIKeys
});

const addServiceAccountRoute = createRoute({
getParentRoute: () => apiKeysRoute,
path: '/add',
component: AddServiceAccount
});

const serviceAccountRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/api-keys/$id',
component: () => <div>Service User</div>
});

interface getRootTreeOptions {
customScreens?: CustomScreen[];
}
Expand All @@ -323,7 +336,8 @@ export function getRootTree({ customScreens = [] }: getRootTreeOptions) {
billingRoute.addChildren([switchBillingCycleModalRoute]),
plansRoute.addChildren([planDowngradeRoute]),
tokensRoute,
apiKeysRoute,
apiKeysRoute.addChildren([addServiceAccountRoute]),
serviceAccountRoute,
...customScreens.map(cc =>
createRoute({
path: cc.path,
Expand Down

0 comments on commit 2b502d0

Please sign in to comment.