From b9071fc7058730cdde941c24909ac2ea79a3128e Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Thu, 18 Jun 2026 09:59:18 +0530 Subject: [PATCH 1/2] feat: add slots in service account views --- .../components/add-token-form.tsx | 42 +++++++++++-------- .../service-account-details-view.tsx | 9 +++- .../service-accounts-view.tsx | 42 +++++++++++++------ web/sdk/shared/types.ts | 12 ++++++ 4 files changed, 73 insertions(+), 32 deletions(-) diff --git a/web/sdk/client/views/service-accounts/components/add-token-form.tsx b/web/sdk/client/views/service-accounts/components/add-token-form.tsx index 02bbe2f61..c9ac34101 100644 --- a/web/sdk/client/views/service-accounts/components/add-token-form.tsx +++ b/web/sdk/client/views/service-accounts/components/add-token-form.tsx @@ -13,6 +13,7 @@ import { type ServiceUserToken } from '@raystack/proton/frontier'; import { useFrontier } from '~/client/contexts/FrontierContext'; +import type { Slot } from '~/shared/types'; import styles from '../service-account-details-view.module.css'; const tokenSchema = yup @@ -26,9 +27,14 @@ type FormData = yup.InferType; export interface AddTokenFormProps { serviceUserId: string; onAddToken: (token: ServiceUserToken) => void; + generateKeyButton?: Slot<{ loading: boolean; disabled: boolean }>; } -export function AddTokenForm({ serviceUserId, onAddToken }: AddTokenFormProps) { +export function AddTokenForm({ + serviceUserId, + onAddToken, + generateKeyButton +}: AddTokenFormProps) { const { activeOrganization } = useFrontier(); const orgId = activeOrganization?.id || ''; @@ -78,24 +84,24 @@ export function AddTokenForm({ serviceUserId, onAddToken }: AddTokenFormProps) { error={errors.title && String(errors.title?.message)} className={styles.addTokenInput} > - + - + {generateKeyButton ? ( + generateKeyButton({ loading: isSubmitting, disabled: isSubmitting }) + ) : ( + + )} ); diff --git a/web/sdk/client/views/service-accounts/service-account-details-view.tsx b/web/sdk/client/views/service-accounts/service-account-details-view.tsx index b3e7d42ca..6bdc08be5 100644 --- a/web/sdk/client/views/service-accounts/service-account-details-view.tsx +++ b/web/sdk/client/views/service-accounts/service-account-details-view.tsx @@ -31,7 +31,7 @@ import { PERMISSIONS, shouldShowComponent } from '~/utils'; import { useServiceUserTokens } from './hooks/useServiceUserTokens'; import { ViewContainer } from '~/client/components/view-container'; import { ViewHeader } from '~/client/components/view-header'; -import { AddTokenForm } from './components/add-token-form'; +import { AddTokenForm, type AddTokenFormProps } from './components/add-token-form'; import { RevokeTokenDialog, type RevokeTokenPayload @@ -53,13 +53,17 @@ export interface ServiceAccountDetailsViewProps { serviceAccountsLabel?: string; onNavigateToServiceAccounts?: () => void; onDeleteSuccess?: () => void; + slots?: { + generateKeyButton?: AddTokenFormProps['generateKeyButton']; + }; } export function ServiceAccountDetailsView({ serviceAccountId, serviceAccountsLabel = 'Service accounts', onNavigateToServiceAccounts, - onDeleteSuccess + onDeleteSuccess, + slots }: ServiceAccountDetailsViewProps) { const { activeOrganization: organization } = useFrontier(); const t = useTerminology(); @@ -166,6 +170,7 @@ export function ServiceAccountDetailsView({ )} diff --git a/web/sdk/client/views/service-accounts/service-accounts-view.tsx b/web/sdk/client/views/service-accounts/service-accounts-view.tsx index 159983af6..9e24da57e 100644 --- a/web/sdk/client/views/service-accounts/service-accounts-view.tsx +++ b/web/sdk/client/views/service-accounts/service-accounts-view.tsx @@ -16,7 +16,6 @@ import { } from '@raystack/apsara'; import deleteIcon from '~/client/assets/delete.svg'; import keyIcon from '~/client/assets/key.svg'; -import exclamationTriangleIcon from '~/client/assets/exclamation-triangle.svg'; import { useQuery } from '@connectrpc/connect-query'; import { create } from '@bufbuild/protobuf'; import { @@ -31,6 +30,7 @@ import { PERMISSIONS, shouldShowComponent } from '~/utils'; import { DEFAULT_DATE_FORMAT } from '~/client/utils/constants'; import { ViewContainer } from '~/client/components/view-container'; import { ViewHeader } from '~/client/components/view-header'; +import type { Slot } from '~/shared/types'; import { getColumns, type ServiceAccountMenuPayload @@ -50,10 +50,14 @@ const manageAccessDialogHandle = Dialog.createHandle(); export interface ServiceAccountsViewProps { onServiceAccountClick?: (id: string) => void; + slots?: { + addAccountButton?: Slot<{ onClick: () => void; disabled: boolean }>; + }; } export function ServiceAccountsView({ - onServiceAccountClick + onServiceAccountClick, + slots }: ServiceAccountsViewProps) { const { activeOrganization: organization, @@ -126,6 +130,8 @@ export function ServiceAccountsView({ [dateFormat, canUpdateWorkspace] ); + const handleAddServiceAccount = () => addDialogHandle.open(null); + const handleCreated = (serviceUserId: string) => { onServiceAccountClick?.(serviceUserId); }; @@ -167,15 +173,22 @@ export function ServiceAccountsView({ heading="No Service Account Found" subHeading={`Create a new account to use the APIs of ${t.appName()} platform`} primaryAction={ - + slots?.addAccountButton ? ( + slots.addAccountButton({ + onClick: handleAddServiceAccount, + disabled: false + }) + ) : ( + + ) } /> ) : ( @@ -207,6 +220,11 @@ export function ServiceAccountsView({ {isLoading ? ( + ) : slots?.addAccountButton ? ( + slots.addAccountButton({ + onClick: handleAddServiceAccount, + disabled: !canUpdateWorkspace + }) ) : ( addDialogHandle.open(null)} + onClick={handleAddServiceAccount} disabled={!canUpdateWorkspace} data-test-id="frontier-sdk-add-service-account-btn" > diff --git a/web/sdk/shared/types.ts b/web/sdk/shared/types.ts index d9ea47f43..b73af1957 100644 --- a/web/sdk/shared/types.ts +++ b/web/sdk/shared/types.ts @@ -2,6 +2,18 @@ import React from 'react'; import { BasePlan } from '../src/types'; import { ThemeProviderProps } from '@raystack/apsara'; +/** + * A render-prop slot: receives a context object (shape defined per view/slot) + * and returns the node to render. Lets a consumer override an internal element + * of a view while keeping the customisation on their side. + * + * @example + * interface ViewSlots { + * addButton?: Slot<{ onClick: () => void; disabled: boolean }>; + * } + */ +export type Slot = (context: TContext) => React.ReactNode; + export interface FrontierClientBillingOptions { supportEmail?: string; successUrl?: string; From 05c83991b9756a251036e49d1cc68e2be62488db Mon Sep 17 00:00:00 2001 From: Rohan Chakraborty Date: Thu, 18 Jun 2026 10:34:39 +0530 Subject: [PATCH 2/2] feat: add onClick --- .../service-accounts/components/add-token-form.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/web/sdk/client/views/service-accounts/components/add-token-form.tsx b/web/sdk/client/views/service-accounts/components/add-token-form.tsx index c9ac34101..78029bab6 100644 --- a/web/sdk/client/views/service-accounts/components/add-token-form.tsx +++ b/web/sdk/client/views/service-accounts/components/add-token-form.tsx @@ -27,7 +27,11 @@ type FormData = yup.InferType; export interface AddTokenFormProps { serviceUserId: string; onAddToken: (token: ServiceUserToken) => void; - generateKeyButton?: Slot<{ loading: boolean; disabled: boolean }>; + generateKeyButton?: Slot<{ + onClick: () => void; + loading: boolean; + disabled: boolean; + }>; } export function AddTokenForm({ @@ -87,7 +91,11 @@ export function AddTokenForm({ {generateKeyButton ? ( - generateKeyButton({ loading: isSubmitting, disabled: isSubmitting }) + generateKeyButton({ + onClick: handleSubmit(onSubmit), + loading: isSubmitting, + disabled: isSubmitting + }) ) : (