diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx
index ed4b60b565..c6d2ea7874 100644
--- a/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx
+++ b/apps/customer-portal/webapp/src/components/project-details/deployments/ManageProductModal.tsx
@@ -79,6 +79,11 @@ export default function ManageProductModal({
const [cores, setCores] = useState("");
const [tps, setTps] = useState("");
const [description, setDescription] = useState("");
+ const [addUpdateState, setAddUpdateState] = useState<{
+ canAdd: boolean;
+ isSaving: boolean;
+ handleAdd: () => void;
+ } | null>(null);
const patchProduct = usePatchDeploymentProduct();
const isSubmitting = patchProduct.isPending;
@@ -308,7 +313,7 @@ export default function ManageProductModal({
}
isLoading={false}
onSaveUpdates={handleSaveUpdates}
- onClose={handleClose}
+ onFormStateChange={setAddUpdateState}
/>
)}
@@ -319,7 +324,7 @@ export default function ManageProductModal({
@@ -343,6 +348,28 @@ export default function ManageProductModal({
Save Changes
))}
+ {tabValue === 1 && addUpdateState && (
+ addUpdateState.isSaving ? (
+ }
+ disabled
+ >
+ Adding...
+
+ ) : (
+
+ )
+ )}
);
diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx
index 91d09a21d7..5b819737f8 100644
--- a/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx
+++ b/apps/customer-portal/webapp/src/components/project-details/deployments/UpdateHistoryTab.tsx
@@ -27,6 +27,7 @@ import {
import { SquarePen, Trash2 } from "@wso2/oxygen-ui-icons-react";
import {
useCallback,
+ useEffect,
useMemo,
useState,
type ChangeEvent,
@@ -42,7 +43,11 @@ export interface UpdateHistoryTabProps {
productVersion: string;
isLoading?: boolean;
onSaveUpdates: (updates: ProductUpdate[]) => Promise;
- onClose: () => void;
+ onFormStateChange?: (state: {
+ canAdd: boolean;
+ isSaving: boolean;
+ handleAdd: () => void;
+ }) => void;
}
interface UpdateFormData {
@@ -69,7 +74,7 @@ export default function UpdateHistoryTab({
productVersion,
isLoading,
onSaveUpdates,
- onClose,
+ onFormStateChange,
}: UpdateHistoryTabProps): JSX.Element {
const [form, setForm] = useState(INITIAL_FORM);
const [editingIndex, setEditingIndex] = useState(null);
@@ -184,6 +189,19 @@ export default function UpdateHistoryTab({
}
}, [form, updates, onSaveUpdates]);
+ const isFormValid = !!form.updateLevel && !!form.date;
+
+ // Notify parent of form state changes
+ useEffect(() => {
+ if (onFormStateChange) {
+ onFormStateChange({
+ canAdd: isFormValid && !isSaving,
+ isSaving,
+ handleAdd: handleAddUpdate,
+ });
+ }
+ }, [isFormValid, isSaving, handleAddUpdate, onFormStateChange]);
+
const handleEditClick = useCallback(
(update: ProductUpdate) => {
const index = updates.findIndex(
@@ -425,32 +443,19 @@ export default function UpdateHistoryTab({
)}
-
-
-
-
+ {!onFormStateChange && (
+
+
+
+ )}
);
@@ -489,6 +494,10 @@ function TimelineItem({
}: TimelineItemProps): JSX.Element {
const [editForm, setEditForm] = useState(update);
+ useEffect(() => {
+ setEditForm(update);
+ }, [update, isEditing]);
+
const handleEditChange =
(field: keyof ProductUpdate) => (e: ChangeEvent) => {
const value = e.target.value;
diff --git a/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx b/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx
index b0adbc2362..070a3826da 100644
--- a/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx
+++ b/apps/customer-portal/webapp/src/components/project-details/deployments/__tests__/UpdateHistoryTab.test.tsx
@@ -39,7 +39,6 @@ describe("UpdateHistoryTab", () => {
productVersion: "2.1.0",
isLoading: false,
onSaveUpdates: vi.fn(),
- onClose: vi.fn(),
};
it("renders update history timeline", () => {
diff --git a/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx b/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx
index 5fe1d3988d..3c53df8d94 100644
--- a/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx
+++ b/apps/customer-portal/webapp/src/pages/ProjectDetails.tsx
@@ -31,6 +31,7 @@ import ProjectDeployments from "@components/project-details/deployments/ProjectD
import ProjectTimeTracking from "@components/project-details/time-tracking/ProjectTimeTracking";
import useGetProjectDetails from "@api/useGetProjectDetails";
import { useGetProjectStat } from "@api/useGetProjectStat";
+import useInfiniteProjects, { flattenProjectPages } from "@api/useGetProjects";
import { useLogger } from "@hooks/useLogger";
import { useLoader } from "@context/linear-loader/LoaderContext";
@@ -52,6 +53,14 @@ export default function ProjectDetails(): JSX.Element {
sidebarCollapsed: boolean;
}>() || { sidebarCollapsed: false };
+ const { data: projectsData } = useInfiniteProjects({ enabled: true });
+ const allProjects = flattenProjectPages(projectsData);
+ const currentProject = useMemo(
+ () => allProjects.find((p) => p.id === projectId),
+ [allProjects, projectId],
+ );
+ const projectTypeLabel = currentProject?.type?.label;
+
const {
data: project,
isFetching: isProjectFetching,
@@ -89,7 +98,6 @@ export default function ProjectDetails(): JSX.Element {
}
}, [projectError, statsError, logger]);
- const projectTypeLabel = project?.type?.label;
const hideDeploymentsAndTimeTracking =
projectTypeLabel === PROJECT_TYPE_LABELS.CLOUD_SUPPORT ||
projectTypeLabel === PROJECT_TYPE_LABELS.CLOUD_EVALUATION_SUPPORT;
@@ -185,7 +193,7 @@ export default function ProjectDetails(): JSX.Element {
};
return (
- <>
+
{/* project page tabs */}
{/* project page content */}
- {renderContent()}
- >
+ {renderContent()}
+
);
}