diff --git a/desktop/src/features/projects/ui/ProjectCards.test.mjs b/desktop/src/features/projects/ui/ProjectCards.test.mjs
new file mode 100644
index 00000000000..ec302837baa
--- /dev/null
+++ b/desktop/src/features/projects/ui/ProjectCards.test.mjs
@@ -0,0 +1,49 @@
+import assert from "node:assert/strict";
+import { after, afterEach, before, test } from "node:test";
+
+import { JSDOM } from "jsdom";
+
+const dom = new JSDOM("
", {
+ url: "http://localhost",
+});
+
+before(() => {
+ Object.assign(globalThis, {
+ document: dom.window.document,
+ HTMLElement: dom.window.HTMLElement,
+ IS_REACT_ACT_ENVIRONMENT: true,
+ window: dom.window,
+ });
+ dom.window.matchMedia = () => ({
+ matches: false,
+ addEventListener() {},
+ removeEventListener() {},
+ });
+});
+
+afterEach(async () => {
+ const { cleanup } = await import("@testing-library/react");
+ cleanup();
+});
+
+after(() => dom.window.close());
+
+test("empty projects state renders a create-project action that fires the provided callback", async () => {
+ const { createElement } = await import("react");
+ const { fireEvent, render, screen } = await import("@testing-library/react");
+ const { EmptyState } = await import("./ProjectCards.tsx");
+
+ let createClicked = false;
+ render(
+ createElement(EmptyState, {
+ onCreateProject: () => {
+ createClicked = true;
+ },
+ }),
+ );
+
+ assert.ok(screen.getByText("No projects yet"));
+ const createButton = screen.getByRole("button", { name: "Create project" });
+ fireEvent.click(createButton);
+ assert.equal(createClicked, true);
+});
diff --git a/desktop/src/features/projects/ui/ProjectCards.tsx b/desktop/src/features/projects/ui/ProjectCards.tsx
index 09535fdfc77..ba48b0297b8 100644
--- a/desktop/src/features/projects/ui/ProjectCards.tsx
+++ b/desktop/src/features/projects/ui/ProjectCards.tsx
@@ -327,7 +327,11 @@ function RepositoryUnavailableIndicator({
);
}
-export function EmptyState() {
+export function EmptyState({
+ onCreateProject,
+}: {
+ onCreateProject: () => void;
+}) {
return (
@@ -337,6 +341,9 @@ export function EmptyState() {
Projects published to this relay will appear here.
+
);
}
diff --git a/desktop/src/features/projects/ui/ProjectsView.tsx b/desktop/src/features/projects/ui/ProjectsView.tsx
index 78ba09934de..6cbe18055cb 100644
--- a/desktop/src/features/projects/ui/ProjectsView.tsx
+++ b/desktop/src/features/projects/ui/ProjectsView.tsx
@@ -15,7 +15,10 @@ import {
useProjectsWorkItemsQuery,
} from "@/features/projects/hooks";
import { useRepositoryActivitySummariesQuery } from "@/features/projects/repositoryActivityHooks";
-import { useCreateProjectMutation } from "@/features/projects/useCreateProject";
+import {
+ type CreateProjectInput,
+ useCreateProjectMutation,
+} from "@/features/projects/useCreateProject";
import { selectProjectRepository } from "@/features/projects/projectModels";
import { useProjectsRepoSnapshotsQuery } from "@/features/projects/useProjectsRepoSnapshots";
import {
@@ -573,6 +576,23 @@ export function ProjectsView() {
[deleteProjectMutation],
);
+ const handleCreateProject = React.useCallback(
+ async (input: CreateProjectInput) => {
+ const result = await createProjectMutation.mutateAsync(input);
+ if (result.compatibilityWarning) {
+ toast.warning("Created as a standalone project", {
+ description: result.compatibilityWarning,
+ });
+ } else {
+ toast.success(`Project "${result.project.name}" created.`);
+ }
+ // Land on the complete project list after creation.
+ handleRepositoryScopeChange("all");
+ handleFilterChange("projects");
+ },
+ [createProjectMutation, handleFilterChange, handleRepositoryScopeChange],
+ );
+
if (projectsQuery.isLoading) {
return ;
}
@@ -593,7 +613,17 @@ export function ProjectsView() {
}
if (projects.length === 0) {
- return ;
+ return (
+ <>
+ setCreateProjectOpen(true)} />
+
+ >
+ );
}
const projectItems =
@@ -790,19 +820,7 @@ export function ProjectsView() {
{createMenu}
{
- const result = await createProjectMutation.mutateAsync(input);
- if (result.compatibilityWarning) {
- toast.warning("Created as a standalone project", {
- description: result.compatibilityWarning,
- });
- } else {
- toast.success(`Project "${result.project.name}" created.`);
- }
- // Land on the complete project list after creation.
- handleRepositoryScopeChange("all");
- handleFilterChange("projects");
- }}
+ onCreate={handleCreateProject}
onOpenChange={setCreateProjectOpen}
open={createProjectOpen}
/>