Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/eight-knives-cross.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@inkeep/agents-manage-ui": patch
---

- Replace prop-drilled permission flags (`readOnly`, `canEdit`, `canUse`, `canManage`) with direct consumption via `useProjectPermissionsQuery()` hook in client components
- Fix empty breadcrumb on `/[tenantId]/profile` page
17 changes: 17 additions & 0 deletions agents-manage-ui/src/app/[tenantId]/profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Metadata } from 'next';
import { PageHeader } from '@/components/layout/page-header';
import { STATIC_LABELS } from '@/constants/theme';

export const metadata = {
title: STATIC_LABELS['profile'],
description: 'Manage your personal preferences.',
} satisfies Metadata;

export default function Layout({ children }: LayoutProps<'/[tenantId]/profile'>) {
return (
<div className="space-y-6">
<PageHeader title={metadata.title} description={metadata.description} />
{children}
</div>
);
}
21 changes: 6 additions & 15 deletions agents-manage-ui/src/app/[tenantId]/profile/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use client';

import { useEffect, useState } from 'react';
import { PageHeader } from '@/components/layout/page-header';
import { Skeleton } from '@/components/ui/skeleton';
import { ProfileForm } from '@/components/user-profile/ProfileForm';
import { useAuthSession } from '@/hooks/use-auth';
Expand All @@ -22,23 +21,15 @@ export default function ProfileSettingsPage() {

if (!user || isLoading) {
return (
<div className="space-y-6">
<PageHeader title="Profile" description="Manage your personal preferences." />
<div className="space-y-4 max-w-sm">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<Skeleton className="h-10 w-16" />
<div className="space-y-4 max-w-sm">
<div className="space-y-2">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-10 w-full" />
</div>
<Skeleton className="h-10 w-16" />
</div>
);
}

return (
<div className="space-y-6">
<PageHeader title="Profile" description="Manage your personal preferences." />
<ProfileForm userId={user.id} initialTimezone={profile?.timezone ?? null} />
</div>
);
return <ProfileForm userId={user.id} initialTimezone={profile?.timezone ?? null} />;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1 @@
import { fetchProjectPermissions } from '@/lib/api/projects';
import { SkillEditModal } from './skill-edit-modal';

export default async function Page(
_props: PageProps<'/[tenantId]/projects/[projectId]/skills/[skillId]/edit'>
) {
const { tenantId, projectId } = await _props.params;
const permissions = await fetchProjectPermissions(tenantId, projectId);

return <SkillEditModal readOnly={!permissions.canEdit} />;
}
export { SkillEditModal as default } from './skill-edit-modal';
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ import {
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { useProjectPermissionsQuery } from '@/lib/query/projects';

interface SkillEditModalProps {
readOnly: boolean;
}

export const SkillEditModal: FC<SkillEditModalProps> = ({ readOnly }) => {
export const SkillEditModal: FC = () => {
const router = useRouter();
const {
data: { canEdit },
} = useProjectPermissionsQuery();

return (
<Dialog open onOpenChange={router.back}>
<DialogContent className="sm:max-w-2xl">
<DialogHeader>
<DialogTitle>{readOnly ? 'View skill' : 'Edit skill'}</DialogTitle>
<DialogTitle>{canEdit ? 'Edit skill' : 'View skill'}</DialogTitle>
<DialogDescription className="sr-only">
{readOnly ? 'View skill details.' : 'Edit skill details.'}
{canEdit ? 'Edit skill details.' : 'View skill details.'}
</DialogDescription>
</DialogHeader>
<SkillForm onSuccess={router.back} readOnly={readOnly} />
<SkillForm onSuccess={router.back} />
</DialogContent>
</Dialog>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ async function ApiKeysPage({ params }: PageProps<'/[tenantId]/projects/[projectI
const { tenantId, projectId } = await params;

try {
const [apiKeys, agent, permissions] = await Promise.all([
const [apiKeys, agent, { canUse }] = await Promise.all([
fetchApiKeys(tenantId, projectId),
fetchAgents(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);
const agentLookup = createLookup(agent.data);
const agentOptions = createAgentOptions(agent.data);
const canUse = permissions.canUse;
return (
<>
<PageHeaderRoot>
Expand All @@ -70,7 +69,7 @@ async function ApiKeysPage({ params }: PageProps<'/[tenantId]/projects/[projectI
</p>
</AlertDescription>
</Alert>
<ApiKeysTable apiKeys={apiKeys.data} agentLookup={agentLookup} canUse={canUse} />
<ApiKeysTable apiKeys={apiKeys.data} agentLookup={agentLookup} />
</>
);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,21 @@ async function AppsPage({ params }: PageProps<'/[tenantId]/projects/[projectId]/
const { tenantId, projectId } = await params;

try {
const [apps, agents, permissions] = await Promise.all([
const [apps, agents, { canUse }] = await Promise.all([
fetchApps(tenantId, projectId),
fetchAgents(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);
const agentLookup = createLookup(agents.data);
const agentOptions = createAgentOptions(agents.data);
const canUse = permissions.canUse;
return (
<>
<PageHeader
title={metadata.title}
description={metadata.description}
action={canUse ? <NewAppDialog agentOptions={agentOptions} /> : undefined}
/>
<AppsTable
apps={apps.data}
agentLookup={agentLookup}
agentOptions={agentOptions}
canUse={canUse}
/>
<AppsTable apps={apps.data} agentLookup={agentLookup} agentOptions={agentOptions} />
</>
);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ArtifactComponentForm } from '@/components/artifact-components/form/artifact-component-form';
import FullPageError from '@/components/errors/full-page-error';
import { fetchArtifactComponent } from '@/lib/api/artifact-components';
import { fetchProjectPermissions } from '@/lib/api/projects';
import { serializeJson } from '@/lib/utils';
import { getErrorCode } from '@/lib/utils/error-serialization';

Expand All @@ -12,19 +11,17 @@ export default async function ArtifactComponentPage({
}: PageProps<'/[tenantId]/projects/[projectId]/artifacts/[artifactComponentId]'>) {
const { artifactComponentId, tenantId, projectId } = await params;
try {
const [artifact, permissions] = await Promise.all([
fetchArtifactComponent(tenantId, projectId, artifactComponentId),
fetchProjectPermissions(tenantId, projectId),
]);

const { name, description, props, render } = artifact;
const { name, description, props, render } = await fetchArtifactComponent(
tenantId,
projectId,
artifactComponentId
);

return (
<ArtifactComponentForm
tenantId={tenantId}
projectId={projectId}
id={artifactComponentId}
readOnly={!permissions.canEdit}
defaultValues={{
id: artifactComponentId,
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,11 @@ async function ArtifactComponentsPage({
}: PageProps<'/[tenantId]/projects/[projectId]/artifacts'>) {
const { tenantId, projectId } = await params;
try {
const [{ data }, permissions] = await Promise.all([
const [{ data }, { canEdit }] = await Promise.all([
fetchArtifactComponents(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);

const canEdit = permissions.canEdit;

return data.length ? (
<>
<PageHeader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { DataComponentForm } from '@/components/data-components/form/data-component-form';
import FullPageError from '@/components/errors/full-page-error';
import { fetchDataComponent } from '@/lib/api/data-components';
import { fetchProjectPermissions } from '@/lib/api/projects';
import { serializeJson } from '@/lib/utils';
import { getErrorCode } from '@/lib/utils/error-serialization';

Expand All @@ -13,20 +12,18 @@ export default async function DataComponentPage({
const { tenantId, projectId, dataComponentId } = await params;

try {
const [dataComponent, permissions] = await Promise.all([
fetchDataComponent(tenantId, projectId, dataComponentId),
fetchProjectPermissions(tenantId, projectId),
]);

const { name, description, props, render } = dataComponent;
const { name, description, props, render } = await fetchDataComponent(
tenantId,
projectId,
dataComponentId
);

return (
<DataComponentForm
className="max-w-2xl mx-auto"
tenantId={tenantId}
projectId={projectId}
id={dataComponentId}
readOnly={!permissions.canEdit}
defaultValues={{
id: dataComponentId,
name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,11 @@ async function DataComponentsPage({
const { tenantId, projectId } = await params;

try {
const [{ data }, permissions] = await Promise.all([
const [{ data }, { canEdit }] = await Promise.all([
fetchDataComponents(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);

const canEdit = permissions.canEdit;

return data.length ? (
<>
<PageHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,10 @@ async function CredentialsPage({
const { tenantId, projectId } = await params;

try {
const [credentials, permissions] = await Promise.all([
const [credentials, { canEdit }] = await Promise.all([
fetchCredentials(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);
const canEdit = permissions.canEdit;
return credentials.length ? (
<>
<PageHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,11 @@ async function ExternalAgentsPage({
const { tenantId, projectId } = await params;

try {
const [externalAgents, permissions] = await Promise.all([
const [externalAgents, { canEdit }] = await Promise.all([
fetchExternalAgents(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);

const canEdit = permissions.canEdit;

return externalAgents.length ? (
<>
<PageHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ async function MCPServersPage({
const { tenantId, projectId } = await params;

try {
const [tools, permissions] = await Promise.all([
const [tools, { canEdit }] = await Promise.all([
fetchMCPTools(tenantId, projectId, { skipDiscovery: true }),
fetchProjectPermissions(tenantId, projectId),
]);
const canEdit = permissions.canEdit;
return tools.length ? (
<>
<PageHeader
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1 @@
import { ProjectMembersWrapper } from '@/components/access/project-members-wrapper';
import FullPageError from '@/components/errors/full-page-error';
import { fetchProjectPermissions } from '@/lib/api/projects';
import { getErrorCode } from '@/lib/utils/error-serialization';

export const dynamic = 'force-dynamic';

export default async function MembersPage({
params,
}: PageProps<'/[tenantId]/projects/[projectId]/members'>) {
const { tenantId, projectId } = await params;

try {
const permissions = await fetchProjectPermissions(tenantId, projectId);

return (
<ProjectMembersWrapper
projectId={projectId}
tenantId={tenantId}
canManage={permissions.canEdit}
/>
);
} catch (error) {
return (
<FullPageError
errorCode={getErrorCode(error)}
link={`/${tenantId}/projects`}
linkText="Back to projects"
context="project"
/>
);
}
}
export { ProjectMembersWrapper as default } from '@/components/access/project-members-wrapper';
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
import type { FC } from 'react';
import { SkillForm } from '@/components/skills/form/skill-form';
import { fetchProjectPermissions } from '@/lib/api/projects';

const EditSkillsPage: FC<
PageProps<'/[tenantId]/projects/[projectId]/skills/[skillId]/edit'>
> = async ({ params }) => {
const { tenantId, projectId } = await params;
const permissions = await fetchProjectPermissions(tenantId, projectId);
return <SkillForm readOnly={!permissions.canEdit} />;
};

export default EditSkillsPage;
export { SkillForm as default } from '@/components/skills/form/skill-form';
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ const SkillsPage: FC<PageProps<'/[tenantId]/projects/[projectId]/skills'>> = asy
const { tenantId, projectId } = await params;

try {
const [skills, permissions] = await Promise.all([
const [skills, { canEdit }] = await Promise.all([
fetchSkills(tenantId, projectId),
fetchProjectPermissions(tenantId, projectId),
]);

const action = permissions.canEdit ? (
const action = canEdit ? (
<Button asChild className="flex items-center gap-2">
<NextLink href={`/${tenantId}/projects/${projectId}/skills/new`}>
<Plus />
Expand Down
Loading
Loading