diff --git a/console/apps/webapp/src/Providers/GlobalProviders/GlobalProviders.tsx b/console/apps/webapp/src/Providers/GlobalProviders/GlobalProviders.tsx
index 97ccd3e9..a3691286 100644
--- a/console/apps/webapp/src/Providers/GlobalProviders/GlobalProviders.tsx
+++ b/console/apps/webapp/src/Providers/GlobalProviders/GlobalProviders.tsx
@@ -18,8 +18,8 @@
import { AuthProvider } from "@agent-management-platform/auth";
import { ClientProvider } from "@agent-management-platform/api-client";
+import { NotificationProvider, ConfirmationDialogProvider } from "@agent-management-platform/shared-component";
import { OxygenUIThemeProvider } from "@wso2/oxygen-ui";
-import { ConfirmationDialogProvider } from "@agent-management-platform/shared-component";
export const GlobalProviders = ({
children,
@@ -28,11 +28,13 @@ export const GlobalProviders = ({
}) => {
return (
-
-
- {children}
-
-
+
+
+
+ {children}
+
+
+
);
};
diff --git a/console/common/config/rush/pnpm-lock.yaml b/console/common/config/rush/pnpm-lock.yaml
index 03713b39..4593afd8 100644
--- a/console/common/config/rush/pnpm-lock.yaml
+++ b/console/common/config/rush/pnpm-lock.yaml
@@ -718,6 +718,9 @@ importers:
'@agent-management-platform/api-client':
specifier: workspace:*
version: link:../../libs/api-client
+ '@agent-management-platform/shared-component':
+ specifier: workspace:*
+ version: link:../../libs/shared-component
'@agent-management-platform/types':
specifier: workspace:*
version: link:../../libs/types
diff --git a/console/workspaces/libs/shared-component/src/components/BuildPanel.tsx b/console/workspaces/libs/shared-component/src/components/BuildPanel.tsx
index 944eea0a..5f8e8d76 100644
--- a/console/workspaces/libs/shared-component/src/components/BuildPanel.tsx
+++ b/console/workspaces/libs/shared-component/src/components/BuildPanel.tsx
@@ -21,6 +21,7 @@ import { Wrench } from "@wso2/oxygen-ui-icons-react";
import { Box, Button, Typography } from "@wso2/oxygen-ui";
import { FormProvider, useForm } from "react-hook-form";
import { TextInput, DrawerHeader, DrawerContent } from "@agent-management-platform/views";
+import { useNotification } from "../providers";
interface BuildPanelProps {
onClose: () => void;
@@ -41,41 +42,41 @@ export function BuildPanel({
agentName,
}: BuildPanelProps) {
const { mutate: buildAgent, isPending } = useBuildAgent();
+ const { notify } = useNotification();
const { data: agent, isLoading: isLoadingAgent } = useGetAgent({
orgName,
projName,
agentName,
- }); const methods = useForm({
+ });
+
+ const methods = useForm({
defaultValues: {
branch: "main",
commitId: "",
},
});
- const handleBuild = async () => {
- try {
- const formData = methods.getValues();
- buildAgent({
- params: {
- orgName,
- projName,
- agentName,
- },
- query: {
- commitId: formData.commitId || "",
- },
- }, {
- onSuccess: () => {
- onClose();
- },
- onError: (error) => {
- console.error("Build trigger failed:", error);
- },
- });
- }
- catch (error) {
- console.error("Build trigger failed:", error);
- }
+ const handleBuild = () => {
+ const formData = methods.getValues();
+ buildAgent({
+ params: {
+ orgName,
+ projName,
+ agentName,
+ },
+ query: {
+ commitId: formData.commitId || "",
+ },
+ }, {
+ onSuccess: () => {
+ notify('success', 'Build triggered successfully');
+ onClose();
+ },
+ onError: (error) => {
+ const message = error instanceof Error ? error.message : 'Build trigger failed';
+ notify('error', message);
+ },
+ });
};
return (
diff --git a/console/workspaces/libs/shared-component/src/components/ConfirmDialog.tsx b/console/workspaces/libs/shared-component/src/components/ConfirmDialog.tsx
new file mode 100644
index 00000000..69b5b40d
--- /dev/null
+++ b/console/workspaces/libs/shared-component/src/components/ConfirmDialog.tsx
@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import {
+ Dialog,
+ DialogTitle,
+ DialogContent,
+ DialogActions,
+ Button,
+ Typography,
+ CircularProgress,
+} from '@wso2/oxygen-ui';
+
+export interface ConfirmDialogProps {
+ open: boolean;
+ title: string;
+ message: string;
+ confirmLabel?: string;
+ cancelLabel?: string;
+ confirmColor?: 'error' | 'primary' | 'secondary';
+ onConfirm: () => void;
+ onCancel: () => void;
+ isLoading?: boolean;
+}
+
+/**
+ * Reusable confirmation dialog for destructive or important actions.
+ * Use for delete confirmations, irreversible operations, etc.
+ */
+export function ConfirmDialog({
+ open,
+ title,
+ message,
+ confirmLabel = 'Confirm',
+ cancelLabel = 'Cancel',
+ confirmColor = 'error',
+ onConfirm,
+ onCancel,
+ isLoading = false,
+}: ConfirmDialogProps) {
+ return (
+
+ );
+}
diff --git a/console/workspaces/libs/shared-component/src/components/DeploymentConfig.tsx b/console/workspaces/libs/shared-component/src/components/DeploymentConfig.tsx
index 7c3567fd..33124bee 100644
--- a/console/workspaces/libs/shared-component/src/components/DeploymentConfig.tsx
+++ b/console/workspaces/libs/shared-component/src/components/DeploymentConfig.tsx
@@ -24,6 +24,7 @@ import { EnvironmentVariable } from "./EnvironmentVariable";
import type { Environment, EnvironmentVariable as EnvVar } from "@agent-management-platform/types";
import { useEffect } from "react";
import { TextInput, DrawerHeader, DrawerContent } from "@agent-management-platform/views";
+import { useNotification } from "../providers";
interface DeploymentConfigProps {
onClose: () => void;
@@ -49,6 +50,7 @@ export function DeploymentConfig({
imageId,
}: DeploymentConfigProps) {
const { mutate: deployAgent, isPending } = useDeployAgent();
+ const { notify } = useNotification();
const { data: agent, isLoading: isLoadingAgent } = useGetAgent({
orgName,
projName,
@@ -77,34 +79,36 @@ export function DeploymentConfig({
});
}, [configurations, methods]);
- const handleDeploy = async () => {
- try {
- const formData = methods.getValues();
+ const handleDeploy = () => {
+ const formData = methods.getValues();
- const envVariables: EnvVar[] = formData.env
- .filter((envVar: { key: string; value: string }) => envVar.key && envVar.value)
- .map((envVar: { key: string; value: string }) => ({
- key: envVar.key,
- value: envVar.value,
- }));
- deployAgent({
- params: {
- orgName,
- projName,
- agentName,
- },
- body: {
- imageId: imageId,
- env: envVariables.length > 0 ? envVariables : undefined,
- },
- }, {
- onSuccess: () => {
- onClose();
- },
- });
- } catch {
- // Error handling is done by the mutation
- }
+ const envVariables: EnvVar[] = formData.env
+ .filter((envVar: { key: string; value: string }) => envVar.key && envVar.value)
+ .map((envVar: { key: string; value: string }) => ({
+ key: envVar.key,
+ value: envVar.value,
+ }));
+
+ deployAgent({
+ params: {
+ orgName,
+ projName,
+ agentName,
+ },
+ body: {
+ imageId: imageId,
+ env: envVariables.length > 0 ? envVariables : undefined,
+ },
+ }, {
+ onSuccess: () => {
+ notify('success', 'Deployment started successfully');
+ onClose();
+ },
+ onError: (error) => {
+ const message = error instanceof Error ? error.message : 'Deployment failed';
+ notify('error', message);
+ },
+ });
};
diff --git a/console/workspaces/libs/shared-component/src/components/index.ts b/console/workspaces/libs/shared-component/src/components/index.ts
index efe16980..a1812ef6 100644
--- a/console/workspaces/libs/shared-component/src/components/index.ts
+++ b/console/workspaces/libs/shared-component/src/components/index.ts
@@ -18,6 +18,7 @@
export * from './BuildLogs';
export * from './BuildPanel';
+export * from './ConfirmDialog';
export * from './BuildSteps';
export * from './CodeBlock';
export * from './DeploymentConfig';
diff --git a/console/workspaces/libs/shared-component/src/index.ts b/console/workspaces/libs/shared-component/src/index.ts
index 700fb529..efa59110 100644
--- a/console/workspaces/libs/shared-component/src/index.ts
+++ b/console/workspaces/libs/shared-component/src/index.ts
@@ -17,3 +17,4 @@
*/
export * from './components';
+export * from './providers';
diff --git a/console/workspaces/libs/shared-component/src/providers/NotificationContext.ts b/console/workspaces/libs/shared-component/src/providers/NotificationContext.ts
new file mode 100644
index 00000000..6c30dd27
--- /dev/null
+++ b/console/workspaces/libs/shared-component/src/providers/NotificationContext.ts
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { createContext } from 'react';
+
+// Notification severity types matching MUI Alert
+export type NotificationType = 'success' | 'error' | 'warning' | 'info';
+
+// Shape of the context value
+export interface NotificationContextType {
+ notify: (type: NotificationType, message: string) => void;
+}
+
+// Context with null default - useNotification hook will throw if used outside provider
+export const NotificationContext = createContext(null);
diff --git a/console/workspaces/libs/shared-component/src/providers/NotificationProvider.tsx b/console/workspaces/libs/shared-component/src/providers/NotificationProvider.tsx
new file mode 100644
index 00000000..50dbf718
--- /dev/null
+++ b/console/workspaces/libs/shared-component/src/providers/NotificationProvider.tsx
@@ -0,0 +1,74 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import React, { useCallback, useMemo, useState } from 'react';
+import { Alert, Snackbar } from '@wso2/oxygen-ui';
+import { NotificationContext, NotificationType } from './NotificationContext';
+
+interface Notification {
+ id: string;
+ type: NotificationType;
+ message: string;
+}
+
+interface NotificationProviderProps {
+ children: React.ReactNode;
+}
+
+const AUTO_HIDE_DURATION = 5000;
+
+export function NotificationProvider({ children }: NotificationProviderProps) {
+ const [notifications, setNotifications] = useState([]);
+
+ // Add a new notification
+ const notify = useCallback((type: NotificationType, message: string) => {
+ const id = Date.now().toString();
+ setNotifications((prev) => [...prev, { id, type, message }]);
+ }, []);
+
+ // Remove a notification by id
+ const removeNotification = useCallback((id: string) => {
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
+ }, []);
+
+ // Memoize context value to prevent unnecessary re-renders
+ const contextValue = useMemo(() => ({ notify }), [notify]);
+
+ return (
+
+ {children}
+ {/* Render notifications - show only the first one, queue the rest */}
+ {notifications.length > 0 && (
+ removeNotification(notifications[0].id)}
+ anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
+ >
+ removeNotification(notifications[0].id)}
+ sx={{ width: '100%' }}
+ >
+ {notifications[0].message}
+
+
+ )}
+
+ );
+}
diff --git a/console/workspaces/libs/shared-component/src/providers/index.ts b/console/workspaces/libs/shared-component/src/providers/index.ts
new file mode 100644
index 00000000..6d78f3af
--- /dev/null
+++ b/console/workspaces/libs/shared-component/src/providers/index.ts
@@ -0,0 +1,21 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+export * from './NotificationContext';
+export * from './NotificationProvider';
+export * from './useNotification';
diff --git a/console/workspaces/libs/shared-component/src/providers/useNotification.ts b/console/workspaces/libs/shared-component/src/providers/useNotification.ts
new file mode 100644
index 00000000..04838beb
--- /dev/null
+++ b/console/workspaces/libs/shared-component/src/providers/useNotification.ts
@@ -0,0 +1,34 @@
+/**
+ * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
+ *
+ * WSO2 LLC. licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { useContext } from 'react';
+import { NotificationContext } from './NotificationContext';
+
+/**
+ * Hook to access notification functions.
+ * Must be used within a NotificationProvider.
+ */
+export function useNotification() {
+ const context = useContext(NotificationContext);
+
+ if (!context) {
+ throw new Error('useNotification must be used within a NotificationProvider');
+ }
+
+ return context;
+}
diff --git a/console/workspaces/pages/add-new-agent/package.json b/console/workspaces/pages/add-new-agent/package.json
index 8391b176..f13a0f49 100644
--- a/console/workspaces/pages/add-new-agent/package.json
+++ b/console/workspaces/pages/add-new-agent/package.json
@@ -48,6 +48,7 @@
"@wso2/oxygen-ui-icons-react": "0.0.1-alpha.9",
"@agent-management-platform/views": "workspace:*",
"@agent-management-platform/api-client": "workspace:*",
+ "@agent-management-platform/shared-component": "workspace:*",
"@agent-management-platform/types": "workspace:*",
"dayjs": "1.11.18",
"yup": "1.4.0",
diff --git a/console/workspaces/pages/add-new-agent/src/AddNewAgent.tsx b/console/workspaces/pages/add-new-agent/src/AddNewAgent.tsx
index b99895e6..287f67b0 100644
--- a/console/workspaces/pages/add-new-agent/src/AddNewAgent.tsx
+++ b/console/workspaces/pages/add-new-agent/src/AddNewAgent.tsx
@@ -25,6 +25,7 @@ import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { addAgentSchema, type AddAgentFormValues } from './form/schema';
import { useCreateAgent, useListAgents } from '@agent-management-platform/api-client';
+import { useNotification } from '@agent-management-platform/shared-component';
import { AgentFlowRouter } from './components/AgentFlowRouter';
import { CreateButtons } from './components/CreateButtons';
import { useAgentFlow } from './hooks/useAgentFlow';
@@ -32,6 +33,7 @@ import { buildAgentCreationPayload } from './utils/buildAgentPayload';
export const AddNewAgent: React.FC = () => {
const navigate = useNavigate();
+ const { notify } = useNotification();
const { orgId, projectId } = useParams<{ orgId: string; projectId?: string }>();
const methods = useForm({
resolver: yupResolver(addAgentSchema),
@@ -80,6 +82,7 @@ export const AddNewAgent: React.FC = () => {
createAgent(payload, {
onSuccess: () => {
+ notify('success', 'Agent created successfully');
navigate(generatePath(
absoluteRouteMap.children.org.children.projects.children.agents.path,
{
@@ -91,12 +94,11 @@ export const AddNewAgent: React.FC = () => {
);
},
onError: (e: unknown) => {
- // TODO: Show error toast/notification to user
- // eslint-disable-next-line no-console
- console.error('Failed to create agent:', e);
+ const message = e instanceof Error ? e.message : 'Failed to create agent';
+ notify('error', message);
}
});
- }, [createAgent, navigate, params]);
+ }, [createAgent, navigate, notify, params]);
const handleAddAgent = useMemo(() => methods.handleSubmit(onSubmit), [methods, onSubmit]);
diff --git a/console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx b/console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx
index fdb151be..f7500368 100644
--- a/console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx
+++ b/console/workspaces/pages/overview/src/AgentsList/AgentsList.tsx
@@ -61,10 +61,10 @@ import {
useDeleteAgent,
useGetProject,
} from "@agent-management-platform/api-client";
+import { useConfirmationDialog, useNotification } from "@agent-management-platform/shared-component";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import { AgentTypeSummery } from "./subComponents/AgentTypeSummery";
-import { useConfirmationDialog } from "@agent-management-platform/shared-component";
dayjs.extend(relativeTime);
@@ -123,20 +123,42 @@ export const AgentsList: React.FC = () => {
projName: projectId,
});
const { mutate: deleteAgent, isPending: isDeletingAgent } = useDeleteAgent();
+ const { notify } = useNotification();
const { data: project, isLoading: isProjectLoading } = useGetProject({
orgName: orgId,
projName: projectId,
});
const { addConfirmation } = useConfirmationDialog();
+
const handleDeleteAgent = useCallback(
- (agentId: string) => {
- deleteAgent({
- orgName: orgId,
- projName: projectId,
- agentName: agentId,
+ (agentId: string, agentName: string) => {
+ addConfirmation({
+ title: "Delete Agent?",
+ description: `Are you sure you want to delete the agent "${agentName}"? This action cannot be undone.`,
+ onConfirm: () => {
+ deleteAgent(
+ {
+ orgName: orgId,
+ projName: projectId,
+ agentName: agentId,
+ },
+ {
+ onSuccess: () => {
+ notify("success", `Agent "${agentName}" deleted successfully`);
+ },
+ onError: (err) => {
+ const message = err instanceof Error ? err.message : "Failed to delete agent";
+ notify("error", message);
+ },
+ }
+ );
+ },
+ confirmButtonColor: "error",
+ confirmButtonIcon: ,
+ confirmButtonText: "Delete",
});
},
- [deleteAgent, orgId, projectId]
+ [addConfirmation, deleteAgent, orgId, projectId, notify]
);
const handleRowMouseEnter = useCallback(
@@ -300,16 +322,7 @@ export const AgentsList: React.FC = () => {
size="small"
onClick={(e) => {
e.stopPropagation(); // Prevent row click if any
- addConfirmation({
- title: "Delete Agent?",
- description: `Are you sure you want to delete the agent "${row.displayName}"? This action cannot be undone.`,
- onConfirm: () => {
- handleDeleteAgent(row.name);
- },
- confirmButtonColor: "error",
- confirmButtonIcon: ,
- confirmButtonText: "Delete",
- });
+ handleDeleteAgent(row.name, row.displayName);
}}
>
Delete
@@ -333,7 +346,6 @@ export const AgentsList: React.FC = () => {
theme.palette.primary.main,
hoveredAgentId,
isTouchDevice,
- addConfirmation,
handleDeleteAgent,
]
);