Skip to content
Source
Sign inSign up

useNewProject.ts

136 lines4.5 KB

1import { scopeProjectRef } from "@t3tools/client-runtime/environment";
2import {
3 getNewProjectRepository,
4 type NewProjectPublishTarget,
5} from "@t3tools/client-runtime/operations/projects";
6import {
7 isAtomCommandInterrupted,
8 squashAtomCommandFailure,
9} from "@t3tools/client-runtime/state/runtime";
10import type { EnvironmentId } from "@t3tools/contracts";
11​
12import { stackedThreadToast, toastManager } from "~/components/ui/toast";
13import { waitForProject } from "~/state/entities";
14import { projectEnvironment } from "~/state/projects";
15import { sourceControlEnvironment } from "~/state/sourceControl";
16import { useAtomCommand } from "~/state/use-atom-command";
17import { useNewThreadHandler } from "./useHandleNewThread";
18import { suggestAppAccessForRemote } from "../components/sourceControlAppAccessToast";
19​
20function errorMessage(error: unknown): string {
21 return error instanceof Error && error.message.trim().length > 0
22 ? error.message
23 : "An error occurred.";
25​
26export function useNewProject() {
27 const createNew = useAtomCommand(projectEnvironment.createNew, { reportFailure: false });
28 const publishRepository = useAtomCommand(sourceControlEnvironment.publishRepository, {
29 reportFailure: false,
30 });
31 const handleNewThread = useNewThreadHandler();
32​
33 const publishNewRepository = async (input: {
34 readonly environmentId: EnvironmentId;
35 readonly workspaceRoot: string;
36 readonly target: NewProjectPublishTarget;
37 }) => {
38 const { label } = input.target.definition;
39 const result = await publishRepository({
40 environmentId: input.environmentId,
41 input: {
42 cwd: input.workspaceRoot,
43 provider: input.target.definition.kind,
44 repository: getNewProjectRepository(input.target, input.workspaceRoot),
45 visibility: "private",
46 },
47 });
48 if (result._tag === "Failure") {
49 if (!isAtomCommandInterrupted(result)) {
50 toastManager.add(
51 stackedThreadToast({
52 type: "error",
53 title: `Could not create the ${label} repository`,
54 description: `${errorMessage(squashAtomCommandFailure(result))} Use Publish Repository in the Git menu to try again.`,
55 }),
56 );
57 }
58 return;
59 }
60 toastManager.add(
61 stackedThreadToast({
62 type: "success",
63 title: `Published to ${label}`,
64 description: result.value.repository.nameWithOwner,
65 }),
66 );
67 void suggestAppAccessForRemote(input.environmentId, result.value.remoteUrl);
68 };
69​
70 return async (input: {
71 readonly environmentId: EnvironmentId;
72 readonly name: string;
73 readonly publishTo: NewProjectPublishTarget | null;
74 }): Promise<boolean> => {
75 const result = await createNew({
76 environmentId: input.environmentId,
77 input: { name: input.name },
78 });
79 if (result._tag === "Failure") {
80 if (!isAtomCommandInterrupted(result)) {
81 toastManager.add(
82 stackedThreadToast({
83 type: "error",
84 title: "Could not create the project",
85 description: errorMessage(squashAtomCommandFailure(result)),
86 }),
87 );
88 }
89 return false;
90 }
91​
92 const { projectId, workspaceRoot, commitError } = result.value;
93 toastManager.add(
94 stackedThreadToast(
95 commitError === undefined
96 ? { type: "success", title: `Created ${input.name}`, description: workspaceRoot }
97 : {
98 type: "warning",
99 title: `Created ${input.name} without a first commit`,
100 description: `${commitError} The project is in ${workspaceRoot}.`,
101 },
102 ),
103 );
104 if (input.publishTo) {
105 void publishNewRepository({
106 environmentId: input.environmentId,
107 workspaceRoot,
108 target: input.publishTo,
109 });
110 }
111​
112 const projectRef = scopeProjectRef(input.environmentId, projectId);
113 // Wait for the stored project path; drafts use it as their key.
114 const project = await waitForProject(projectRef).catch((error: unknown) => {
115 toastManager.add(
116 stackedThreadToast({
117 type: "error",
118 title: "Failed to open project",
119 description: `${errorMessage(error)} It will appear in the sidebar once this client catches up.`,
120 }),
121 );
122 return null;
123 });
124 if (project === null) return true;
125 await handleNewThread(projectRef).catch((error: unknown) => {
126 toastManager.add(
127 stackedThreadToast({
128 type: "error",
129 title: "Failed to open project",
130 description: errorMessage(error),
131 }),
132 );
133 });
134 return true;
135 };