();
+const manageAccessDialogHandle = Dialog.createHandle();
+
+export interface ServiceAccountDetailsViewProps {
+ serviceAccountId: string;
+ serviceAccountsLabel?: string;
+ onNavigateToServiceAccounts?: () => void;
+ onDeleteSuccess?: () => void;
+}
+
+export function ServiceAccountDetailsView({
+ serviceAccountId,
+ serviceAccountsLabel = 'Service accounts',
+ onNavigateToServiceAccounts,
+ onDeleteSuccess
+}: ServiceAccountDetailsViewProps) {
+ const { activeOrganization: organization } = useFrontier();
+ const t = useTerminology();
+ const orgId = organization?.id || '';
+
+ const { data: serviceUser, isLoading: isServiceUserLoading } = useQuery(
+ FrontierServiceQueries.getServiceUser,
+ create(GetServiceUserRequestSchema, {
+ id: serviceAccountId,
+ orgId
+ }),
+ {
+ enabled: Boolean(serviceAccountId) && Boolean(orgId),
+ select: data => data?.serviceuser
+ }
+ );
+
+ const {
+ tokens: serviceUserTokens,
+ isLoading: isTokensLoading,
+ addToken,
+ removeToken
+ } = useServiceUserTokens({
+ id: serviceAccountId,
+ orgId,
+ enableFetch: true
+ });
+
+ const resource = `app/organization:${orgId}`;
+ const listOfPermissionsToCheck = useMemo(
+ () => [
+ { permission: PERMISSIONS.UpdatePermission, resource }
+ ],
+ [resource]
+ );
+
+ const { permissions, isFetching: isPermissionsFetching } = usePermissions(
+ listOfPermissionsToCheck,
+ !!orgId
+ );
+
+ const canUpdateWorkspace = useMemo(
+ () =>
+ shouldShowComponent(
+ permissions,
+ `${PERMISSIONS.UpdatePermission}::${resource}`
+ ),
+ [permissions, resource]
+ );
+
+ const isLoading = isServiceUserLoading || isTokensLoading || isPermissionsFetching;
+
+ const serviceAccountTitle = serviceUser?.title || '';
+
+ const handleDeleteSuccess = useCallback(() => {
+ onDeleteSuccess?.();
+ }, [onDeleteSuccess]);
+
+ return (
+
+
+ {
+ e.preventDefault();
+ onNavigateToServiceAccounts?.();
+ }}
+ >
+ {serviceAccountsLabel}
+
+
+
+ {isServiceUserLoading ? (
+
+ ) : (
+ serviceAccountTitle
+ )}
+
+
+ }
+ >
+ {!isLoading && canUpdateWorkspace && (
+
+ )}
+
+
+
+ {isServiceUserLoading ? (
+
+ ) : (
+
+ Create API key for accessing {t.appName()} and its features
+
+ )}
+
+
+
+ {serviceUserTokens.length > 0 && (
+
+ )}
+
+
+
+
+
+
+ );
+}
+
+interface ActionsMenuProps {
+ serviceAccountId: string;
+}
+
+function ActionsMenu({ serviceAccountId }: ActionsMenuProps) {
+ return (
+ <>
+
+ }
+ >
+
+
+
+ >
+ );
+}
+
+function TokenList({
+ tokens,
+ isLoading
+}: {
+ tokens: ServiceUserToken[];
+ isLoading: boolean;
+}) {
+ if (isLoading) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+ {tokens.map(token => (
+
+ ))}
+
+ );
+}
+
+function TokenItem({ token }: { token: ServiceUserToken }) {
+ const [isCopied, setIsCopied] = useState(false);
+ const { copy } = useCopyToClipboard();
+
+ const encodedToken = 'Basic ' + btoa(`${token?.id}:${token?.token}`);
+
+ async function onCopy() {
+ const res = await copy(encodedToken);
+ if (res) {
+ setIsCopied(true);
+ setTimeout(() => setIsCopied(false), 1000);
+ }
+ }
+
+ return (
+
+
+
+ {token?.title}
+
+
+
+ {token?.token ? (
+
+
+ Note: Please save your key securely, it cannot be recovered after
+ leaving this page
+
+
+
+ {encodedToken}
+
+ {isCopied ? (
+
+ ) : (
+
+ )}
+
+
+ ) : null}
+
+ );
+}
diff --git a/web/sdk/react/views-new/service-accounts/service-accounts-view.module.css b/web/sdk/react/views-new/service-accounts/service-accounts-view.module.css
new file mode 100644
index 000000000..71f576f85
--- /dev/null
+++ b/web/sdk/react/views-new/service-accounts/service-accounts-view.module.css
@@ -0,0 +1,7 @@
+.tableRoot {
+ border: none;
+}
+
+.menuContent {
+ min-width: 180px;
+}
diff --git a/web/sdk/react/views-new/service-accounts/service-accounts-view.tsx b/web/sdk/react/views-new/service-accounts/service-accounts-view.tsx
new file mode 100644
index 000000000..caaaeaae7
--- /dev/null
+++ b/web/sdk/react/views-new/service-accounts/service-accounts-view.tsx
@@ -0,0 +1,283 @@
+'use client';
+
+import { useMemo, useState } from 'react';
+import { ExclamationTriangleIcon, KeyboardIcon, TrashIcon } from '@radix-ui/react-icons';
+import {
+ Button,
+ Tooltip,
+ Skeleton,
+ Flex,
+ EmptyState,
+ DataTable,
+ Dialog,
+ AlertDialog,
+ Menu
+} from '@raystack/apsara-v1';
+import { useQuery } from '@connectrpc/connect-query';
+import { create } from '@bufbuild/protobuf';
+import {
+ FrontierServiceQueries,
+ ListOrganizationServiceUsersRequestSchema
+} from '@raystack/proton/frontier';
+import { useFrontier } from '../../contexts/FrontierContext';
+import { usePermissions } from '../../hooks/usePermissions';
+import { useTerminology } from '../../hooks/useTerminology';
+import { AuthTooltipMessage } from '../../utils';
+import { PERMISSIONS, shouldShowComponent } from '../../../utils';
+import { DEFAULT_DATE_FORMAT } from '../../utils/constants';
+import { ViewContainer } from '../../components/view-container';
+import { ViewHeader } from '../../components/view-header';
+import {
+ getColumns,
+ type ServiceAccountMenuPayload
+} from './components/service-account-columns';
+import { AddServiceAccountDialog } from './components/add-service-account-dialog';
+import {
+ DeleteServiceAccountDialog,
+ type DeleteServiceAccountPayload
+} from './components/delete-service-account-dialog';
+import { ManageProjectAccessDialog } from './components/manage-project-access-dialog';
+import styles from './service-accounts-view.module.css';
+
+const serviceAccountMenuHandle = Menu.createHandle();
+const addDialogHandle = Dialog.createHandle();
+const deleteDialogHandle = AlertDialog.createHandle();
+const manageAccessDialogHandle = Dialog.createHandle();
+
+export interface ServiceAccountsViewProps {
+ onServiceAccountClick?: (id: string) => void;
+}
+
+export function ServiceAccountsView({
+ onServiceAccountClick
+}: ServiceAccountsViewProps) {
+ const {
+ activeOrganization: organization,
+ isActiveOrganizationLoading,
+ config
+ } = useFrontier();
+ const t = useTerminology();
+
+ const resource = `app/organization:${organization?.id}`;
+ const listOfPermissionsToCheck = useMemo(
+ () => [
+ {
+ permission: PERMISSIONS.UpdatePermission,
+ resource
+ }
+ ],
+ [resource]
+ );
+
+ const { permissions, isFetching: isPermissionsFetching } = usePermissions(
+ listOfPermissionsToCheck,
+ !!organization?.id
+ );
+
+ const canUpdateWorkspace = useMemo(
+ () =>
+ shouldShowComponent(
+ permissions,
+ `${PERMISSIONS.UpdatePermission}::${resource}`
+ ),
+ [permissions, resource]
+ );
+
+ const orgId = organization?.id ?? '';
+ const [manageAccessServiceUserId, setManageAccessServiceUserId] = useState('');
+
+ const {
+ data: serviceUsersData,
+ isLoading: isServiceUsersLoading,
+ refetch
+ } = useQuery(
+ FrontierServiceQueries.listOrganizationServiceUsers,
+ create(ListOrganizationServiceUsersRequestSchema, {
+ id: orgId
+ }),
+ {
+ enabled: Boolean(orgId) && canUpdateWorkspace
+ }
+ );
+
+ const serviceUsers = useMemo(
+ () => serviceUsersData?.serviceusers ?? [],
+ [serviceUsersData]
+ );
+
+ const isPermissionsLoading =
+ isActiveOrganizationLoading || isPermissionsFetching;
+
+ const isLoading = isPermissionsLoading || isServiceUsersLoading;
+
+ const dateFormat = config?.dateFormat || DEFAULT_DATE_FORMAT;
+
+ const columns = useMemo(
+ () =>
+ getColumns({
+ dateFormat,
+ menuHandle: serviceAccountMenuHandle,
+ canUpdateWorkspace,
+ orgId
+ }),
+ [dateFormat, canUpdateWorkspace, orgId]
+ );
+
+ const handleCreated = (serviceUserId: string) => {
+ onServiceAccountClick?.(serviceUserId);
+ };
+
+ const handleRefetch = () => {
+ refetch();
+ };
+
+ const hasNoAccess = !canUpdateWorkspace && !isPermissionsLoading;
+ const hasNoServiceAccounts =
+ canUpdateWorkspace && !isLoading && serviceUsers.length === 0;
+
+ return (
+
+
+
+ {hasNoAccess ? (
+ }
+ heading="Restricted Access"
+ subHeading="Admin access required, please reach out to your admin incase you want to generate a key."
+ />
+ ) : hasNoServiceAccounts ? (
+ }
+ heading="No Service Account Found"
+ subHeading={`Create a new account to use the APIs of ${t.appName()} platform`}
+ primaryAction={
+
+ }
+ />
+ ) : (
+ onServiceAccountClick?.(row.id)}
+ >
+
+
+
+ {isLoading ? (
+
+ ) : (
+
+ )}
+
+ {isLoading ? (
+
+ ) : (
+
+ }
+ >
+
+
+ {!canUpdateWorkspace && (
+ {AuthTooltipMessage}
+ )}
+
+ )}
+
+ }
+ heading="No service accounts found"
+ subHeading="Try adjusting your search"
+ />
+ }
+ classNames={{
+ root: styles.tableRoot
+ }}
+ />
+
+
+ )}
+
+
+
+
+
+ {manageAccessServiceUserId && (
+
+ )}
+
+ );
+}
diff --git a/web/sdk/react/views-new/teams/components/add-member-menu.tsx b/web/sdk/react/views-new/teams/components/add-member-menu.tsx
index 893672348..2728c204d 100644
--- a/web/sdk/react/views-new/teams/components/add-member-menu.tsx
+++ b/web/sdk/react/views-new/teams/components/add-member-menu.tsx
@@ -19,8 +19,8 @@ import {
} from '@raystack/proton/frontier';
import { create } from '@bufbuild/protobuf';
import { useFrontier } from '../../../contexts/FrontierContext';
-import { AuthTooltipMessage } from '~/react/utils';
-import { filterUsersfromUsers, getInitials } from '~/utils';
+import { AuthTooltipMessage } from '../../../utils';
+import { filterUsersfromUsers, getInitials } from '../../../../utils';
import styles from '../team-details-view.module.css';
interface AddMemberMenuProps {