feat(nextjs): Proper api error handling

This commit is contained in:
shiva raj badu 2025-07-25 11:40:33 +05:45
parent 1ecd5c164b
commit d7c9858365
No known key found for this signature in database
10 changed files with 219 additions and 240 deletions

View file

@ -12,7 +12,7 @@ import { Skeleton } from "@/components/ui/skeleton";
import { Button } from "@/components/ui/button";
import { PresentationGenerationApi } from "../services/api/presentation-generation";
import { getStaticFileUrl } from "../utils/others";
import { toast } from "sonner";
interface IconsEditorProps {
icon_prompt?: string[] | null;
onClose?: () => void;
@ -53,8 +53,9 @@ const IconsEditor = ({
limit: 40,
});
setIcons(data);
} catch (error) {
} catch (error: any) {
console.error("Error fetching icons:", error);
toast.error(error.message || "Failed to fetch icons. Please try again.");
setIcons([]);
} finally {
setLoading(false);

View file

@ -98,10 +98,10 @@ const ImageEditor = ({
try {
const response = await PresentationGenerationApi.getPreviousGeneratedImages();
setPreviousGeneratedImages(response);
} catch (error) {
} catch (error: any) {
toast.error("Failed to get previous generated images. Please try again.");
console.error("error in getting previous generated images", error);
setError("Failed to get previous generated images. Please try again.");
setError(error.message || "Failed to get previous generated images. Please try again.");
}
}
@ -218,9 +218,9 @@ const ImageEditor = ({
});
setPreviewImages(response);
} catch (err) {
} catch (err: any) {
console.error("Error in image generation", err);
setError("Failed to generate image. Please try again.");
setError(err.message || "Failed to generate image. Please try again.");
} finally {
setIsGenerating(false);
}

View file

@ -149,11 +149,13 @@ const DocumentsPreviewPage: React.FC = () => {
dispatch(setPresentationId(createResponse.id));
router.push("/outline");
} catch (error) {
console.error("Error in presentation creation:", error);
toast.error("Error in presentation creation. Please try again.");
} catch (error: any) {
console.error("Error in radar presentation creation:", error);
toast.error('Error', {
description: error.message || "Error in radar presentation creation.",
});
setShowLoading({
message: "Error in presentation creation.",
message: "Error in radar presentation creation.",
show: true,
duration: 10,
progress: false,

View file

@ -86,10 +86,10 @@ export const usePresentationGeneration = (
dispatch(clearPresentationData());
router.push(`/presentation?id=${presentationId}&stream=true`);
}
} catch (error) {
console.error("Error in data generation", error);
} catch (error: any) {
console.error('Error In Presentation Generation(prepare).', error);
toast.error("Generation Error", {
description: "Failed to generate presentation. Please try again.",
description: error.message || "Error In Presentation Generation(prepare).",
});
} finally {
setLoadingState(DEFAULT_LOADING_STATE);

View file

@ -60,9 +60,11 @@ const SlideContent = ({
dispatch(updateSlide({ index: slide.index, slide: response }));
toast.success("Slide updated successfully");
}
} catch (error) {
console.error("Error updating slide:", error);
toast.error("Failed to update slide. Please try again.");
} catch (error: any) {
console.error("Error in slide editing:", error);
toast.error("Error in slide editing.", {
description: error.message || "Error in slide editing.",
});
} finally {
setIsUpdating(false);
}
@ -70,8 +72,11 @@ const SlideContent = ({
const onDeleteSlide = async () => {
try {
dispatch(deletePresentationSlide(slide.index));
} catch (error) {
} catch (error: any) {
console.error("Error deleting slide:", error);
toast.error("Error deleting slide.", {
description: error.message || "Error deleting slide.",
});
}
};
// Scroll to the new slide when streaming and new slides are being generated

View file

@ -53,6 +53,7 @@ export const useAutoSave = ({
} catch (error) {
console.error('❌ Auto-save failed:', error);
} finally {
setIsSaving(false);
}

View file

@ -0,0 +1,121 @@
// API Error Response Interface
interface ApiErrorResponse {
detail?: string;
message?: string;
error?: string;
}
// API Response Handler Utility
export class ApiResponseHandler {
static async handleResponse(response: Response, defaultErrorMessage: string): Promise<any> {
// Handle successful responses
if (response.ok) {
// Handle 204 No Content responses
if (response.status === 204) {
return true;
}
// Try to parse JSON response
try {
return await response.json();
} catch (error) {
// If JSON parsing fails but response is ok, return empty object
return {};
}
}
// Handle error responses
let errorMessage = defaultErrorMessage;
try {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
if (errorData.detail) {
errorMessage = errorData.detail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
errorMessage = errorData.error;
}
} catch (parseError) {
// If JSON parsing fails, use status-based messages
errorMessage = this.getStatusBasedErrorMessage(response.status, defaultErrorMessage);
}
// Throw error with appropriate message
throw new Error(errorMessage);
}
static async handleResponseWithResult(response: Response, defaultErrorMessage: string): Promise<{success: boolean, message?: string}> {
try {
// Handle successful responses
if (response.ok) {
return { success: true };
}
// Handle error responses
let errorMessage = defaultErrorMessage;
try {
const errorData: ApiErrorResponse = await response.json();
// Extract error message in order of preference
if (errorData.detail) {
errorMessage = errorData.detail;
} else if (errorData.message) {
errorMessage = errorData.message;
} else if (errorData.error) {
errorMessage = errorData.error;
}
} catch (parseError) {
// If JSON parsing fails, use status-based messages
errorMessage = this.getStatusBasedErrorMessage(response.status, defaultErrorMessage);
}
return {
success: false,
message: errorMessage,
};
} catch (error) {
return {
success: false,
message: error instanceof Error ? error.message : defaultErrorMessage,
};
}
}
private static getStatusBasedErrorMessage(status: number, defaultMessage: string): string {
switch (status) {
case 400:
return "Bad request. Please check your input and try again.";
case 401:
return "Unauthorized. Please log in and try again.";
case 403:
return "Access forbidden. You don't have permission to perform this action.";
case 404:
return "Resource not found. The requested item may have been deleted or moved.";
case 409:
return "Conflict. The resource already exists or there's a conflict with the current state.";
case 422:
return "Validation error. Please check your input and try again.";
case 429:
return "Too many requests. Please wait a moment and try again.";
case 500:
return "Internal server error. Please try again later.";
case 502:
return "Bad gateway. The server is temporarily unavailable.";
case 503:
return "Service unavailable. Please try again later.";
case 504:
return "Gateway timeout. The request took too long to process.";
default:
return defaultMessage;
}
}
}
export type { ApiErrorResponse };

View file

@ -1,5 +1,7 @@
import { getHeader, getHeaderForFormData } from "./header";
import { IconSearch, ImageGenerate, ImageSearch, PreviousGeneratedImagesResponse } from "./params";
import { ApiResponseHandler } from "./api-error-handler";
export class PresentationGenerationApi {
static async uploadDoc(documents: File[]) {
const formData = new FormData();
@ -19,20 +21,13 @@ export class PresentationGenerationApi {
}
);
if (!response.ok) {
throw new Error(`Upload failed: ${response.statusText}`);
}
const data = await response.json();
return data;
return await ApiResponseHandler.handleResponse(response, "Failed to upload documents");
} catch (error) {
console.error("Upload error:", error);
throw error;
}
}
static async decomposeDocuments(documentKeys: string[]) {
try {
const response = await fetch(
@ -46,28 +41,53 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to decompose files: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to decompose documents");
} catch (error) {
console.error("Error in Decompose Files", error);
throw error;
}
}
static async createPresentation({
prompt,
n_slides,
file_paths,
language,
}: {
prompt: string;
n_slides: number | null;
file_paths?: string[];
language: string | null;
}) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/create`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify({
prompt,
n_slides,
file_paths,
language,
}),
cache: "no-cache",
}
);
return await ApiResponseHandler.handleResponse(response, "Failed to create presentation");
} catch (error) {
console.error("error in presentation creation", error);
throw error;
}
}
static async editSlide(
slide_id: string,
prompt: string
) {
try {
const response = await fetch(
`/api/v1/ppt/slide/edit`,
{
@ -76,18 +96,12 @@ export class PresentationGenerationApi {
body: JSON.stringify({
id: slide_id,
prompt,
}),
cache: "no-cache",
}
);
if (!response.ok) {
throw new Error("Failed to update slides");
}
const data = await response.json();
return data;
return await ApiResponseHandler.handleResponse(response, "Failed to update slide");
} catch (error) {
console.error("error in slide update", error);
throw error;
@ -105,15 +119,8 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(
`Failed to update presentation content: ${response.statusText}`
);
}
return await ApiResponseHandler.handleResponse(response, "Failed to update presentation content");
} catch (error) {
console.error("error in presentation content update", error);
throw error;
@ -131,41 +138,17 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to generate data: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to prepare presentation");
} catch (error) {
console.error("error in data generation", error);
throw error;
}
}
// IMAGE AND ICON SEARCH
static async imageSearch(imageSearch: ImageSearch) {
try {
const response = await fetch(
`/api/v1/ppt/image/search`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify(imageSearch),
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to search images: ${response.statusText}`);
}
} catch (error) {
console.error("error in image search", error);
throw error;
}
}
static async generateImage(imageGenerate: ImageGenerate) {
try {
const response = await fetch(
@ -176,20 +159,15 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to generate images: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to generate image");
} catch (error) {
console.error("error in image generation", error);
throw error;
}
}
static getPreviousGeneratedImages = async():Promise<PreviousGeneratedImagesResponse[]>=>{
static getPreviousGeneratedImages = async (): Promise<PreviousGeneratedImagesResponse[]> => {
try {
const response = await fetch(
`/api/v1/ppt/images/generated`,
@ -198,17 +176,14 @@ export class PresentationGenerationApi {
headers: getHeader(),
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to get previous generated images: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to get previous generated images");
} catch (error) {
console.error("error in getting previous generated images", error);
throw error;
}
}
static async searchIcons(iconSearch: IconSearch) {
try {
const response = await fetch(
@ -219,41 +194,15 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to search icons: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to search icons");
} catch (error) {
console.error("error in icon search", error);
throw error;
}
}
static async updateDocuments(body: any) {
try {
const response = await fetch(
`/api/v1/ppt/document/update`,
{
method: "POST",
headers: getHeaderForFormData(),
body: body,
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to update documents: ${response.statusText}`);
}
} catch (error) {
console.error("error in document update", error);
throw error;
}
}
// EXPORT PRESENTATION
static async exportAsPPTX(presentationData: any) {
@ -267,100 +216,13 @@ export class PresentationGenerationApi {
cache: "no-cache",
}
);
if (response.ok) {
return await response.json();
} else {
throw new Error(`Failed to export as pptx: ${response.statusText}`);
}
return await ApiResponseHandler.handleResponse(response, "Failed to export as PowerPoint");
} catch (error) {
console.error("error in pptx export", error);
throw error;
}
}
static async exportAsPDF(presentationData: any) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/export_as_pdf`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify(presentationData),
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to export as pdf: ${response.statusText}`);
}
} catch (error) {
console.error("error in pdf export", error);
throw error;
}
}
static async deleteSlide(presentation_id: string, slide_id: string) {
try {
const response = await fetch(
`/api/v1/ppt/slide/delete?presentation_id=${presentation_id}&slide_id=${slide_id}`,
{
method: "DELETE",
headers: getHeader(),
cache: "no-cache",
}
);
if (response.status === 204) {
return true;
} else {
throw new Error(`Failed to delete slide: ${response.statusText}`);
}
} catch (error) {
console.error("error in slide deletion", error);
throw error;
}
}
static async createPresentation({
prompt,
n_slides,
file_paths,
language,
}: {
prompt: string;
n_slides: number | null;
file_paths?: string[];
language: string | null;
}) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/create`,
{
method: "POST",
headers: getHeader(),
body: JSON.stringify({
prompt,
n_slides,
file_paths,
language,
}),
cache: "no-cache",
}
);
if (response.ok) {
const data = await response.json();
return data;
} else {
throw new Error(`Failed to get questions: ${response.statusText}`);
}
} catch (error) {
console.error("error in question generation", error);
throw error;
}
}
}
}

View file

@ -162,7 +162,7 @@ const UploadPage = () => {
* Handles errors during presentation generation
*/
const handleGenerationError = (error: any) => {
console.error("Error in presentation generation:", error);
console.error("Error in upload page", error);
setLoadingState({
isLoading: false,
message: "",
@ -170,7 +170,7 @@ const UploadPage = () => {
showProgress: false,
});
toast.error("Error", {
description: "Failed to generate presentation. Please try again.",
description: error.message || "Error in upload page.",
});
};

View file

@ -1,8 +1,7 @@
import {
getHeader,
} from "@/app/(presentation-generator)/services/api/header";
import { ApiResponseHandler } from "@/app/(presentation-generator)/services/api/api-error-handler";
export interface PresentationResponse {
id: string;
@ -33,38 +32,36 @@ export class DashboardApi {
method: "GET",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
} else if (response.status === 404) {
// Handle the special case where 404 means "no presentations found"
if (response.status === 404) {
console.log("No presentations found");
return [];
}
return [];
return await ApiResponseHandler.handleResponse(response, "Failed to fetch presentations");
} catch (error) {
console.error("Error fetching presentations:", error);
throw error;
}
}
static async getPresentation(id: string) {
try {
const response = await fetch(
`/api/v1/ppt/presentation/?id=${id}`,
{
method: "GET",
}
);
if (response.status === 200) {
const data = await response.json();
return data;
}
throw new Error("Presentation not found");
return await ApiResponseHandler.handleResponse(response, "Presentation not found");
} catch (error) {
console.error("Error fetching presentations:", error);
console.error("Error fetching presentation:", error);
throw error;
}
}
static async deletePresentation(presentation_id: string) {
try {
const response = await fetch(
@ -74,17 +71,8 @@ export class DashboardApi {
headers: getHeader(),
}
);
const data = await response.json();
if (response.status === 204) {
return {
success: true,
};
}
return {
success: false,
message: data.detail || "Failed to delete presentation",
};
return await ApiResponseHandler.handleResponseWithResult(response, "Failed to delete presentation");
} catch (error) {
console.error("Error deleting presentation:", error);
return {
@ -93,5 +81,4 @@ export class DashboardApi {
};
}
}
}