| 1 | import { useRouter } from "@tanstack/react-router"; |
| 2 | import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; |
| 3 | import type { ScopedProjectRef } from "@t3tools/contracts"; |
| 4 | import { useCallback } from "react"; |
| 5 | |
| 6 | import { useComposerDraftStore } from "../composerDraftStore"; |
| 7 | import { resolveThreadRouteTarget } from "../threadRoutes"; |
| 8 | import { releaseProjectDraftUploads } from "../lib/composerDraftUploads"; |
| 9 | import { waitForProjectRemoval } from "../state/entities"; |
| 10 | import { projectEnvironment } from "../state/projects"; |
| 11 | import { useAtomCommand } from "../state/use-atom-command"; |
| 12 | import { 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 | */ |
| 20 | export 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 | } |