diff --git a/src/components/CustomErrorToast.tsx b/src/components/CustomErrorToast.tsx
new file mode 100644
index 0000000..6674667
--- /dev/null
+++ b/src/components/CustomErrorToast.tsx
@@ -0,0 +1,80 @@
+import React from "react";
+import { toast } from "sonner";
+import { X, Copy, Check } from "lucide-react";
+
+interface CustomErrorToastProps {
+ message: string;
+ toastId: string | number;
+ copied?: boolean;
+ onCopy?: () => void;
+}
+
+export function CustomErrorToast({
+ message,
+ toastId,
+ copied = false,
+ onCopy,
+}: CustomErrorToastProps) {
+ const handleClose = () => {
+ toast.dismiss(toastId);
+ };
+
+ const handleCopy = () => {
+ if (onCopy) {
+ onCopy();
+ }
+ };
+
+ return (
+
+ {/* Content */}
+
+
+
+
+
+
Error
+
+ {/* Action buttons */}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/hooks/useLoadApp.ts b/src/hooks/useLoadApp.ts
index a0f70be..2b3e4c3 100644
--- a/src/hooks/useLoadApp.ts
+++ b/src/hooks/useLoadApp.ts
@@ -23,7 +23,9 @@ export function useLoadApp(appId: number | null) {
return ipcClient.getApp(appId);
},
enabled: appId !== null,
- meta: { showErrorToast: true },
+ // Deliberately not showing error toast here because
+ // this will pop up when app is deleted.
+ // meta: { showErrorToast: true },
});
useEffect(() => {
diff --git a/src/ipc/handlers/app_handlers.ts b/src/ipc/handlers/app_handlers.ts
index 3ac7b8d..e6097d4 100644
--- a/src/ipc/handlers/app_handlers.ts
+++ b/src/ipc/handlers/app_handlers.ts
@@ -669,24 +669,25 @@ export function registerAppHandlers() {
}
}
+ // Delete app from database
+ try {
+ await db.delete(apps).where(eq(apps.id, appId));
+ // Note: Associated chats will cascade delete
+ } catch (error: any) {
+ logger.error(`Error deleting app ${appId} from database:`, error);
+ throw new Error(
+ `Failed to delete app from database: ${error.message}`,
+ );
+ }
+
// Delete app files
const appPath = getDyadAppPath(app.path);
try {
await fsPromises.rm(appPath, { recursive: true, force: true });
} catch (error: any) {
logger.error(`Error deleting app files for app ${appId}:`, error);
- throw new Error(`Failed to delete app files: ${error.message}`);
- }
-
- // Delete app from database
- try {
- await db.delete(apps).where(eq(apps.id, appId));
- // Note: Associated chats will cascade delete if that's set up in the schema
- return;
- } catch (error: any) {
- logger.error(`Error deleting app ${appId} from database:`, error);
throw new Error(
- `Failed to delete app from database: ${error.message}`,
+ `App deleted from database, but failed to delete app files. Please delete app files from ${appPath} manually.\n\nError: ${error.message}`,
);
}
});
diff --git a/src/ipc/handlers/version_handlers.ts b/src/ipc/handlers/version_handlers.ts
index f00b742..827a53d 100644
--- a/src/ipc/handlers/version_handlers.ts
+++ b/src/ipc/handlers/version_handlers.ts
@@ -22,7 +22,8 @@ export function registerVersionHandlers() {
});
if (!app) {
- throw new Error("App not found");
+ // The app might have just been deleted, so we return an empty array.
+ return [];
}
const appPath = getDyadAppPath(app.path);
diff --git a/src/lib/toast.ts b/src/lib/toast.tsx
similarity index 54%
rename from src/lib/toast.ts
rename to src/lib/toast.tsx
index 8ed344f..8f8856b 100644
--- a/src/lib/toast.ts
+++ b/src/lib/toast.tsx
@@ -1,5 +1,7 @@
import { toast } from "sonner";
import { PostHog } from "posthog-js";
+import React from "react";
+import { CustomErrorToast } from "../components/CustomErrorToast";
/**
* Toast utility functions for consistent notifications across the app
@@ -18,8 +20,54 @@ export const showSuccess = (message: string) => {
* @param message The error message to display
*/
export const showError = (message: any) => {
- toast.error(message.toString());
+ const errorMessage = message.toString();
console.error(message);
+
+ const onCopy = (toastId: string | number) => {
+ navigator.clipboard.writeText(errorMessage);
+
+ // Update the toast to show the 'copied' state
+ toast.custom(
+ (t) => (
+ onCopy(t)}
+ />
+ ),
+ { id: toastId, duration: Infinity },
+ );
+
+ // After 2 seconds, revert the toast back to the original state
+ setTimeout(() => {
+ toast.custom(
+ (t) => (
+ onCopy(t)}
+ />
+ ),
+ { id: toastId, duration: Infinity },
+ );
+ }, 2000);
+ };
+
+ // Use custom error toast with enhanced features
+ const toastId = toast.custom(
+ (t) => (
+ onCopy(t)}
+ />
+ ),
+ { duration: 4000 },
+ );
+
+ return toastId;
};
/**
@@ -39,26 +87,6 @@ export const showInfo = (message: string) => {
toast.info(message);
};
-/**
- * Show a loading toast that can be updated with success/error
- * @param loadingMessage The message to show while loading
- * @param promise The promise to track
- * @param successMessage Optional success message
- * @param errorMessage Optional error message
- */
-export const showLoading = (
- loadingMessage: string,
- promise: Promise,
- successMessage?: string,
- errorMessage?: string,
-) => {
- return toast.promise(promise, {
- loading: loadingMessage,
- success: () => successMessage || "Operation completed successfully",
- error: (err) => errorMessage || `Error: ${err.message || "Unknown error"}`,
- });
-};
-
export const showExtraFilesToast = ({
files,
error,
diff --git a/src/pages/app-details.tsx b/src/pages/app-details.tsx
index c4fce3d..64a021e 100644
--- a/src/pages/app-details.tsx
+++ b/src/pages/app-details.tsx
@@ -86,6 +86,7 @@ export default function AppDetailsPage() {
await refreshApps();
navigate({ to: "/", search: {} });
} catch (error) {
+ setIsDeleteDialogOpen(false);
showError(error);
} finally {
setIsDeleting(false);