1import { useRouter } from "@tanstack/react-router";
2import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
3import type { ScopedProjectRef } from "@t3tools/contracts";
4import { useCallback } from "react";
5
6import { useComposerDraftStore } from "../composerDraftStore";
7import { resolveThreadRouteTarget } from "../threadRoutes";
8import { releaseProjectDraftUploads } from "../lib/composerDraftUploads";
9import { waitForProjectRemoval } from "../state/entities";
10import { projectEnvironment } from "../state/projects";
11import { useAtomCommand } from "../state/use-atom-command";
12import { stackedThreadToast, toastManager } from "../components/ui/toast";
13
14/**
15 * Removes a project whose clone never landed. The server clears the empty
16 * folder along with the clone, so there is nothing to confirm: no threads
17 * exist yet and the draft is the only thing lost, which the user is looking
18 * at when they click.
19 */
20export function useRemoveClonedProject() {
21 const router = useRouter();
22 const deleteProject = useAtomCommand(projectEnvironment.delete, { reportFailure: false });
23
24 return useCallback(
25 async (projectRef: ScopedProjectRef) => {
26 const draftStore = useComposerDraftStore.getState();
27 const result = await deleteProject({
28 environmentId: projectRef.environmentId,
29 // Not forced: a project whose clone never landed has no threads, and
30 // if one appeared in the meantime the server refuses rather than
31 // silently deleting it.
32 input: { projectId: projectRef.projectId },
33 });
34 if (result._tag === "Failure") {
35 const error = squashAtomCommandFailure(result);
36 toastManager.add(
37 stackedThreadToast({
38 type: "error",
39 title: "Failed to remove project",
40 description: error instanceof Error ? error.message : "An error occurred.",
41 }),
42 );
43 return false;
44 }
45 await waitForProjectRemoval(projectRef);
46 // Read the route after the await: the user may have moved on while the
47 // delete was in flight, and only a draft of this project needs to go.
48 const routeParams = router.state.matches[router.state.matches.length - 1]?.params ?? {};
49 const routeTarget = resolveThreadRouteTarget(routeParams);
50 const viewingDraft =
51 routeTarget?.kind === "draft" ? draftStore.getDraftSession(routeTarget.draftId) : null;
52 const viewingThisProject =
53 viewingDraft?.environmentId === projectRef.environmentId &&
54 viewingDraft.projectId === projectRef.projectId;
55 releaseProjectDraftUploads(projectRef);
56 const projectDraft = draftStore.getDraftThreadByProjectRef(projectRef);
57 if (projectDraft) draftStore.clearDraftThread(projectDraft.draftId);
58 draftStore.clearProjectDraftThreadId(projectRef);
59 if (viewingThisProject) void router.navigate({ to: "/", replace: true });
60 return true;
61 },
62 [deleteProject, router],
63 );
64}