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
88 changes: 88 additions & 0 deletions apps/customer-portal/webapp/src/api/useGetDeploymentDocuments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) 2026 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 { useQuery, type UseQueryResult } from "@tanstack/react-query";
import { useAsgardeo } from "@asgardeo/react";
import { useLogger } from "@hooks/useLogger";
import { ApiQueryKeys } from "@constants/apiConstants";
import { useAuthApiClient } from "@context/AuthApiContext";
import type { DeploymentDocument } from "@models/responses";

function normalizeDocuments(raw: unknown): DeploymentDocument[] {
if (Array.isArray(raw)) {
return raw as DeploymentDocument[];
}
if (raw && typeof raw === "object" && "attachments" in raw) {
return (raw as { attachments: DeploymentDocument[] }).attachments ?? [];
}
if (raw && typeof raw === "object" && "documents" in raw) {
return (raw as { documents: DeploymentDocument[] }).documents ?? [];
}
return [];
}

/**
* Fetches deployment documents (GET /deployments/:deploymentId/attachments).
*
* @param {string} deploymentId - The deployment ID.
* @returns {UseQueryResult<DeploymentDocument[], Error>} The query result.
*/
export function useGetDeploymentDocuments(
deploymentId: string,
): UseQueryResult<DeploymentDocument[], Error> {
const logger = useLogger();
const { isSignedIn, isLoading: isAuthLoading } = useAsgardeo();
const fetchFn = useAuthApiClient();

return useQuery<DeploymentDocument[], Error>({
queryKey: [ApiQueryKeys.DEPLOYMENT_ATTACHMENTS, deploymentId],
queryFn: async (): Promise<DeploymentDocument[]> => {
logger.debug(
`Fetching deployment documents for deployment ID: ${deploymentId}`,
);

try {
const baseUrl = window.config?.CUSTOMER_PORTAL_BACKEND_BASE_URL;
if (!baseUrl) {
throw new Error("CUSTOMER_PORTAL_BACKEND_BASE_URL is not configured");
}

const requestUrl = `${baseUrl}/deployments/${deploymentId}/attachments`;
const response = await fetchFn(requestUrl, { method: "GET" });

logger.debug(
`[useGetDeploymentDocuments] Response status: ${response.status}`,
);

if (!response.ok) {
throw new Error(
`Error fetching deployment documents: ${response.statusText}`,
);
}

const raw = await response.json();
const documents = normalizeDocuments(raw);
logger.debug("[useGetDeploymentDocuments] Data received:", documents);
return documents;
} catch (error) {
logger.error("[useGetDeploymentDocuments] Error:", error);
throw error;
}
},
enabled: !!deploymentId && isSignedIn && !isAuthLoading,
staleTime: 5 * 60 * 1000,
});
}
84 changes: 84 additions & 0 deletions apps/customer-portal/webapp/src/api/useGetProducts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright (c) 2026 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 { useQuery, type UseQueryResult } from "@tanstack/react-query";
import { useAsgardeo } from "@asgardeo/react";
import { useLogger } from "@hooks/useLogger";
import { ApiQueryKeys } from "@constants/apiConstants";
import { useAuthApiClient } from "@context/AuthApiContext";
import type { ProductItem } from "@models/responses";

function normalizeProducts(raw: unknown): ProductItem[] {
if (Array.isArray(raw)) {
return raw as ProductItem[];
}
if (raw && typeof raw === "object" && "products" in raw) {
return (raw as { products: ProductItem[] }).products ?? [];
}
return [];
}

/**
* Fetches products list (GET /products).
*
* @param {object} params - offset and limit.
* @returns {UseQueryResult<ProductItem[], Error>} The query result.
*/
export function useGetProducts(params?: {
offset?: number;
limit?: number;
}): UseQueryResult<ProductItem[], Error> {
const { offset = 0, limit = 10 } = params ?? {};
const logger = useLogger();
const { isSignedIn, isLoading: isAuthLoading } = useAsgardeo();
const fetchFn = useAuthApiClient();

return useQuery<ProductItem[], Error>({
queryKey: [ApiQueryKeys.PRODUCTS, offset, limit],
queryFn: async (): Promise<ProductItem[]> => {
logger.debug(`Fetching products offset=${offset} limit=${limit}`);

try {
const baseUrl = window.config?.CUSTOMER_PORTAL_BACKEND_BASE_URL;
if (!baseUrl) {
throw new Error("CUSTOMER_PORTAL_BACKEND_BASE_URL is not configured");
}

const searchParams = new URLSearchParams();
searchParams.set("offset", String(offset));
searchParams.set("limit", String(limit));
const requestUrl = `${baseUrl}/products?${searchParams.toString()}`;
const response = await fetchFn(requestUrl, { method: "GET" });

logger.debug(`[useGetProducts] Response status: ${response.status}`);

if (!response.ok) {
throw new Error(`Error fetching products: ${response.statusText}`);
}

const raw = await response.json();
const products = normalizeProducts(raw);
logger.debug("[useGetProducts] Data received:", products);
return products;
} catch (error) {
logger.error("[useGetProducts] Error:", error);
throw error;
}
},
enabled: isSignedIn && !isAuthLoading,
staleTime: 5 * 60 * 1000,
});
}
106 changes: 106 additions & 0 deletions apps/customer-portal/webapp/src/api/usePatchDeployment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright (c) 2026 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 {
useMutation,
useQueryClient,
type UseMutationResult,
} from "@tanstack/react-query";
import { useAsgardeo } from "@asgardeo/react";
import { useLogger } from "@hooks/useLogger";
import { useAuthApiClient } from "@context/AuthApiContext";
import { ApiQueryKeys } from "@constants/apiConstants";
import type { PatchDeploymentRequest } from "@models/requests";

export interface PatchDeploymentVariables {
projectId: string;
deploymentId: string;
body: PatchDeploymentRequest;
}

/**
* Patches a deployment (PATCH /projects/:projectId/deployments/:deploymentId).
* On success, invalidates project-deployments and deployment queries.
*
* @returns {UseMutationResult<void, Error, PatchDeploymentVariables>} Mutation result.
*/
export function usePatchDeployment(): UseMutationResult<
void,
Error,
PatchDeploymentVariables
> {
const logger = useLogger();
const queryClient = useQueryClient();
const { isSignedIn, isLoading: isAuthLoading } = useAsgardeo();
const fetchFn = useAuthApiClient();

return useMutation<void, Error, PatchDeploymentVariables>({
mutationFn: async ({
projectId,
deploymentId,
body,
}: PatchDeploymentVariables): Promise<void> => {
try {
logger.debug("[usePatchDeployment] Request:", {
projectId,
deploymentId,
body,
});

if (!isSignedIn || isAuthLoading) {
throw new Error(
"User must be signed in to update a deployment",
);
}

const baseUrl = window.config?.CUSTOMER_PORTAL_BACKEND_BASE_URL;
if (!baseUrl) {
throw new Error("CUSTOMER_PORTAL_BACKEND_BASE_URL is not configured");
}

const requestUrl = `${baseUrl}/projects/${projectId}/deployments/${deploymentId}`;
const response = await fetchFn(requestUrl, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});

logger.debug(
"[usePatchDeployment] Response status:",
response.status,
);

if (!response.ok) {
const text = await response.text();
throw new Error(
`Error updating deployment: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`,
);
}
} catch (error) {
logger.error("[usePatchDeployment] Error:", error);
throw error;
}
},
onSuccess: (_data, { projectId }) => {
queryClient.invalidateQueries({
queryKey: ["project-deployments", projectId],
});
queryClient.invalidateQueries({
queryKey: [ApiQueryKeys.DEPLOYMENTS, projectId],
});
},
});
}
104 changes: 104 additions & 0 deletions apps/customer-portal/webapp/src/api/usePatchDeploymentProduct.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2026 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 {
useMutation,
useQueryClient,
type UseMutationResult,
} from "@tanstack/react-query";
import { useAsgardeo } from "@asgardeo/react";
import { useLogger } from "@hooks/useLogger";
import { useAuthApiClient } from "@context/AuthApiContext";
import { ApiQueryKeys } from "@constants/apiConstants";
import type { PatchDeploymentProductRequest } from "@models/requests";

export interface PatchDeploymentProductVariables {
deploymentId: string;
productId: string;
body: PatchDeploymentProductRequest;
}

/**
* Patches a product in a deployment (PATCH /deployments/:deploymentId/products/:productId).
* On success, invalidates deployment-products queries.
*
* @returns {UseMutationResult<void, Error, PatchDeploymentProductVariables>} Mutation result.
*/
export function usePatchDeploymentProduct(): UseMutationResult<
void,
Error,
PatchDeploymentProductVariables
> {
const logger = useLogger();
const queryClient = useQueryClient();
const { isSignedIn, isLoading: isAuthLoading } = useAsgardeo();
const fetchFn = useAuthApiClient();

return useMutation<void, Error, PatchDeploymentProductVariables>({
mutationFn: async ({
deploymentId,
productId,
body,
}: PatchDeploymentProductVariables): Promise<void> => {
try {
logger.debug("[usePatchDeploymentProduct] Request:", {
deploymentId,
productId,
cores: body.cores,
tps: body.tps,
});

if (!isSignedIn || isAuthLoading) {
throw new Error(
"User must be signed in to update a deployment product",
);
}

const baseUrl = window.config?.CUSTOMER_PORTAL_BACKEND_BASE_URL;
if (!baseUrl) {
throw new Error("CUSTOMER_PORTAL_BACKEND_BASE_URL is not configured");
}

const requestUrl = `${baseUrl}/deployments/${deploymentId}/products/${productId}`;
const response = await fetchFn(requestUrl, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});

logger.debug(
"[usePatchDeploymentProduct] Response status:",
response.status,
);

if (!response.ok) {
const text = await response.text();
throw new Error(
`Error updating deployment product: ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`,
);
}
} catch (error) {
logger.error("[usePatchDeploymentProduct] Error:", error);
throw error;
}
},
onSuccess: (_data, { deploymentId }) => {
queryClient.invalidateQueries({
queryKey: [ApiQueryKeys.DEPLOYMENT_PRODUCTS, deploymentId],
});
},
});
}
Loading