fix(web): + Starten for empty projects instead of fallback bot (#92)
* fix(web): show Starten for empty projects instead of fallback bot When a project space had no group conversations, the sidebar fell back to listing 1:1 bots (e.g. Chief from onboarding) under the project name. Bootstrap also redirected /app to the first bot, making it look like an existing conversation. Only show project group chats in Projekte, render + Starten for empty projects, and open the existing create flow (group or bot) on click. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(web): keep empty-project Starten view and route flows correctly Rebased onto main. Guard the Hauptthread auto-redirect when the current space is an empty project so the main Starten CTA stays visible. Split project start into create-bot vs create-group flows (main vs sidebar) and feed home-space bots into the group form when starting from a project. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(web): surface empty-project Starten on Projekte pillar Route bare /app in empty projects to the Projekte view, keep /app on stale bot/group URLs, and show the create-group side panel without an active thread. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): scope RPC to selected space and tighten Starten specs Pass x-rakazo-space-id from Playwright RPC helpers so archive/list hit the active project space. Trim the group-create Starten test to the side panel flow and navigate to /app after archiving the last bot. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * style(e2e): format empty-project-starten spec Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> * fix(e2e): remove project bots so empty spaces stay deletable Archived bots still count as space content and hide the delete-space menu. Use bots/remove in spaces.spec when the test needs a deletable empty project space. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
This commit is contained in:
parent
bc5f41ecb1
commit
23084b460b
73
apps/web/e2e/empty-project-starten.spec.ts
Normal file
73
apps/web/e2e/empty-project-starten.spec.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { completeOnboarding, rpc, signup, unterhaltungenOnly } from "./helpers";
|
||||
|
||||
test("empty project shows Starten instead of a fallback bot", async ({ page }) => {
|
||||
const stamp = Date.now();
|
||||
await signup(page, `empty-project-${stamp}@rakazo.test`, "password12", "Empty Project");
|
||||
await completeOnboarding(page);
|
||||
await page.goto("/app");
|
||||
await page.waitForURL(/\/app\/[^/]+$/);
|
||||
|
||||
const projekte = page.locator(".sidebar-projekte");
|
||||
await projekte.getByTestId("create-project-trigger").click();
|
||||
const dialog = page.getByRole("dialog", { name: "New project" });
|
||||
await dialog.getByLabel("Name").fill("Launch");
|
||||
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
|
||||
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("project-start-main")).toBeVisible();
|
||||
|
||||
await page.getByTestId("project-start-main").click();
|
||||
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create");
|
||||
});
|
||||
|
||||
test("Starten in an onboarded project opens the group create flow", async ({ page }) => {
|
||||
const stamp = Date.now();
|
||||
await signup(page, `project-start-${stamp}@rakazo.test`, "password12", "Project Start");
|
||||
await completeOnboarding(page);
|
||||
|
||||
const projekte = page.locator(".sidebar-projekte");
|
||||
await projekte.getByTestId("create-project-trigger").click();
|
||||
const dialog = page.getByRole("dialog", { name: "New project" });
|
||||
await dialog.getByLabel("Name").fill("Website");
|
||||
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
await page.goto("/onboarding");
|
||||
await completeOnboarding(page);
|
||||
|
||||
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
|
||||
|
||||
await projekte.getByRole("button", { name: "Starten", exact: true }).click();
|
||||
await expect(page.getByTestId("side-panel")).toHaveAttribute("data-panel", "create-group");
|
||||
await expect(page.getByText("New group", { exact: true })).toBeVisible();
|
||||
await expect(
|
||||
page.getByTestId("side-panel").getByRole("button", { name: /^Chief/ }),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("archiving the only bot keeps Starten visible in the project sidebar", async ({ page }) => {
|
||||
const stamp = Date.now();
|
||||
await signup(page, `project-archive-${stamp}@rakazo.test`, "password12", "Project Archive");
|
||||
await completeOnboarding(page);
|
||||
|
||||
const projekte = page.locator(".sidebar-projekte");
|
||||
await projekte.getByTestId("create-project-trigger").click();
|
||||
const dialog = page.getByRole("dialog", { name: "New project" });
|
||||
await dialog.getByLabel("Name").fill("Side");
|
||||
await dialog.getByRole("button", { name: "Create project", exact: true }).click();
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
await page.goto("/onboarding");
|
||||
await completeOnboarding(page);
|
||||
|
||||
const [chief] = await rpc<Array<{ id: string; name: string }>>(page, "bots/list", {});
|
||||
expect(chief?.name).toMatch(/^Chief/);
|
||||
await rpc(page, "bots/archive", { botId: chief!.id });
|
||||
|
||||
await page.goto("/app");
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
|
||||
await expect(page.getByTestId("project-start-main")).toBeVisible();
|
||||
});
|
||||
@ -23,7 +23,11 @@ export function activeBotId(page: Page) {
|
||||
}
|
||||
|
||||
export async function rpc<T>(page: Page, procedure: string, body: unknown): Promise<T> {
|
||||
const response = await page.request.post(`/rpc/${procedure}`, { data: { json: body } });
|
||||
const spaceId = await page.evaluate(() => window.localStorage.getItem("rakazo:space-id"));
|
||||
const response = await page.request.post(`/rpc/${procedure}`, {
|
||||
data: { json: body },
|
||||
headers: spaceId ? { "x-rakazo-space-id": spaceId } : undefined,
|
||||
});
|
||||
const parsed = (await response.json()) as { json?: T; error?: { message?: string } };
|
||||
if (!response.ok() || parsed.error) {
|
||||
throw new Error(`${procedure} ${response.status()}: ${parsed.error?.message ?? "failed"}`);
|
||||
|
||||
@ -41,7 +41,8 @@ test("Projekte + opens new project dialog instead of group form", async ({ page
|
||||
await expect(unterhaltungenOnly(page).getByText("Customer project", { exact: true })).toHaveCount(
|
||||
0,
|
||||
);
|
||||
await expect(page.locator("main").getByRole("button", { name: "Create an Oo" })).toBeVisible();
|
||||
await expect(page.getByTestId("project-start-main")).toBeVisible();
|
||||
await expect(page.getByTestId("project-start-main")).toHaveText(/Starten/);
|
||||
});
|
||||
|
||||
test("spaces stay invisible by default and chat creation requires approval", async ({
|
||||
@ -93,16 +94,18 @@ test("spaces stay invisible by default and chat creation requires approval", asy
|
||||
const supportSpaceGroup = await supportSpace.getAttribute("data-sidebar-group");
|
||||
const supportSpaceId = supportSpaceGroup?.match(/^project-space:([^:]+)/)?.[1];
|
||||
expect(supportSpaceId).toBeTruthy();
|
||||
await supportSpace.getByRole("button", { name: "Open Customer support" }).click();
|
||||
await page.waitForURL(/\/onboarding/);
|
||||
await supportSpace.getByRole("button", { name: "Select Customer support" }).click();
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.localStorage.getItem("rakazo:space-id")))
|
||||
.toBe(supportSpaceId);
|
||||
await page.goto("/onboarding");
|
||||
await completeOnboarding(page);
|
||||
|
||||
await expect(projekte.getByText("Customer support", { exact: true })).toBeVisible();
|
||||
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
|
||||
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
|
||||
await captureScreenshot(page, testInfo, "spaces-sidebar");
|
||||
|
||||
await page.evaluate((id) => {
|
||||
@ -136,21 +139,19 @@ test("a new space auto-completes onboarding and can be deleted from its menu", a
|
||||
const projekte = page.locator(".sidebar-projekte");
|
||||
await expect(projekte.getByText("Temporary", { exact: true })).toBeVisible();
|
||||
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
|
||||
await expect(projekte.getByRole("button", { name: "Starten", exact: true })).toBeVisible();
|
||||
await captureScreenshot(page, testInfo, "new-space-after-onboarding");
|
||||
|
||||
// Spaces can only be deleted when empty — remove auto-created Chief first.
|
||||
const temporaryChief = projekte.getByRole("button", { name: /^Chief/ });
|
||||
await temporaryChief.click({ button: "right" });
|
||||
await page.getByRole("menuitem", { name: "Delete" }).click();
|
||||
const deleteBotDialog = page.getByRole("alertdialog", { name: /Delete Chief/ });
|
||||
await expect(deleteBotDialog).toBeVisible();
|
||||
await deleteBotDialog.getByRole("button", { name: "Delete", exact: true }).click();
|
||||
await expect(projekte.getByRole("button", { name: /^Chief/ })).toHaveCount(0);
|
||||
await expect(unterhaltungenOnly(page).getByRole("button", { name: /^Chief/ })).toHaveCount(1);
|
||||
const [temporaryChief] = await rpc<Array<{ id: string }>>(page, "bots/list", {});
|
||||
expect(temporaryChief).toBeTruthy();
|
||||
await rpc(page, "bots/remove", { botId: temporaryChief!.id, deleteMemories: false });
|
||||
await page.goto("/app");
|
||||
await page.waitForURL(/\/app(?:\/)?$/);
|
||||
|
||||
// The emptied space stays selectable instead of trapping the user.
|
||||
await expect(projekte.getByRole("button", { name: "Open Temporary" }).first()).toBeVisible();
|
||||
await expect(projekte.getByRole("button", { name: "Select Temporary" }).first()).toBeVisible();
|
||||
|
||||
// Deletion is discoverable through the visible Space actions menu.
|
||||
await projectSpace(projekte, "Temporary")
|
||||
@ -196,20 +197,14 @@ test("deleting the last bot in a space stays in the app after first use", async
|
||||
|
||||
// Delete the only bot in the new space: the app must stay put instead of
|
||||
// forcing onboarding again.
|
||||
const sideBot = projekte.getByRole("button", { name: /^Chief/ });
|
||||
await sideBot.click({ button: "right" });
|
||||
await page.getByRole("menuitem", { name: "Delete" }).click();
|
||||
const deleteDialog = page.getByRole("alertdialog", { name: /Delete Chief/ });
|
||||
await expect(deleteDialog).toBeVisible();
|
||||
await deleteDialog.getByRole("button", { name: "Delete", exact: true }).click();
|
||||
await expect(sideBot).toHaveCount(0);
|
||||
const [sideChief] = await rpc<Array<{ id: string }>>(page, "bots/list", {});
|
||||
expect(sideChief).toBeTruthy();
|
||||
await rpc(page, "bots/remove", { botId: sideChief!.id, deleteMemories: false });
|
||||
await expect(page).not.toHaveURL(/\/onboarding/);
|
||||
await page.reload();
|
||||
await page.goto("/app");
|
||||
await expect(page).not.toHaveURL(/\/onboarding/);
|
||||
await expect(projekte.getByRole("button", { name: "Open Side" }).first()).toBeVisible();
|
||||
await expect(
|
||||
page.locator("main").getByRole("button", { name: "Create new Bot", exact: true }),
|
||||
).toBeVisible();
|
||||
await expect(projekte.getByRole("button", { name: "Select Side" }).first()).toBeVisible();
|
||||
await expect(page.getByTestId("project-start-main")).toBeVisible();
|
||||
|
||||
await projectSpace(projekte, "Side").getByRole("button", { name: "Actions for Side" }).click();
|
||||
await expect(page.getByRole("menuitem", { name: "Delete space" })).toBeVisible();
|
||||
|
||||
50
apps/web/src/lib/project-empty-state.test.ts
Normal file
50
apps/web/src/lib/project-empty-state.test.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
isEmptyProjectSpace,
|
||||
isHauptthreadGroup,
|
||||
projectGroupChatsInSpace,
|
||||
} from "./project-empty-state.js";
|
||||
|
||||
describe("isHauptthreadGroup", () => {
|
||||
it("recognizes Hauptthread and Team Chat", () => {
|
||||
expect(isHauptthreadGroup({ id: "1", name: "Hauptthread" })).toBe(true);
|
||||
expect(isHauptthreadGroup({ id: "2", name: "Team Chat" })).toBe(true);
|
||||
expect(isHauptthreadGroup({ id: "3", name: "Launch" })).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("projectGroupChatsInSpace", () => {
|
||||
it("returns project groups in a space without Hauptthread", () => {
|
||||
const groups = [
|
||||
{ id: "g1", name: "Launch", spaceId: "space-a" },
|
||||
{ id: "g2", name: "Hauptthread", spaceId: "space-a" },
|
||||
{ id: "g3", name: "Ops", spaceId: "space-b" },
|
||||
];
|
||||
expect(projectGroupChatsInSpace(groups, "space-a").map((group) => group.id)).toEqual(["g1"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("isEmptyProjectSpace", () => {
|
||||
const spaces = [
|
||||
{ id: "home", isDefault: true },
|
||||
{ id: "project", isDefault: false },
|
||||
];
|
||||
|
||||
it("is false for the home space", () => {
|
||||
expect(isEmptyProjectSpace("home", spaces, [], "home")).toBe(false);
|
||||
});
|
||||
|
||||
it("is true for a project space without group chats", () => {
|
||||
expect(isEmptyProjectSpace("project", spaces, [], "home")).toBe(true);
|
||||
});
|
||||
|
||||
it("is false when the project space already has a group chat", () => {
|
||||
const groups = [{ id: "g1", name: "Launch", spaceId: "project" }];
|
||||
expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores Hauptthread when deciding emptiness", () => {
|
||||
const groups = [{ id: "g1", name: "Hauptthread", spaceId: "project" }];
|
||||
expect(isEmptyProjectSpace("project", spaces, groups, "home")).toBe(true);
|
||||
});
|
||||
});
|
||||
26
apps/web/src/lib/project-empty-state.ts
Normal file
26
apps/web/src/lib/project-empty-state.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { Group, Space } from "@rakazo/contracts";
|
||||
|
||||
export function isHauptthreadGroup(group: Pick<Group, "id" | "name">): boolean {
|
||||
return group.name === "Hauptthread" || group.name === "Team Chat";
|
||||
}
|
||||
|
||||
/** Project-scoped group chats in a space (excludes Hauptthread). */
|
||||
export function projectGroupChatsInSpace(
|
||||
groups: readonly Pick<Group, "id" | "name" | "spaceId">[],
|
||||
spaceId: string,
|
||||
): readonly Pick<Group, "id">[] {
|
||||
return groups.filter((group) => group.spaceId === spaceId && !isHauptthreadGroup(group));
|
||||
}
|
||||
|
||||
/** Non-default project space with no project group conversations yet. */
|
||||
export function isEmptyProjectSpace(
|
||||
spaceId: string | null | undefined,
|
||||
spaces: readonly Pick<Space, "id" | "isDefault">[],
|
||||
groups: readonly Pick<Group, "id" | "name" | "spaceId">[],
|
||||
homeSpaceId: string | undefined,
|
||||
): boolean {
|
||||
if (!spaceId || !homeSpaceId || spaceId === homeSpaceId) return false;
|
||||
const space = spaces.find((item) => item.id === spaceId);
|
||||
if (!space || space.isDefault) return false;
|
||||
return projectGroupChatsInSpace(groups, spaceId).length === 0;
|
||||
}
|
||||
@ -214,6 +214,7 @@ import {
|
||||
revokePendingAttachmentPreviews,
|
||||
} from "../lib/pending-attachments";
|
||||
import { markAfterPaint, markOnce } from "../lib/performance";
|
||||
import { isEmptyProjectSpace } from "../lib/project-empty-state";
|
||||
import { readProjectSidebarOrder, writeProjectSidebarOrder } from "../lib/project-sidebar-order";
|
||||
import { quoteDraftForSelection } from "../lib/quote-selection";
|
||||
import { clearSpaceSelection, rpc, selectedSpaceId, selectSpace } from "../lib/rpc";
|
||||
@ -851,7 +852,15 @@ export function ShellPage() {
|
||||
const inGroup = Boolean(groupId);
|
||||
const projectSessionKey = searchParams.get("s");
|
||||
const voiceEntry = searchParams.get("voice") === "1";
|
||||
const active = inGroup ? undefined : (bots.find((b) => b.id === botId) ?? bots[0]);
|
||||
const currentSpaceId = selectedSpaceId() ?? bootstrapMe?.spaceId;
|
||||
const homeSpaceId = spaces.find((space) => space.isDefault)?.id ?? bootstrapMe?.spaceId;
|
||||
const emptyProjectSpace = useMemo(
|
||||
() => isEmptyProjectSpace(currentSpaceId, spaces, groups, homeSpaceId),
|
||||
[currentSpaceId, groups, homeSpaceId, spaces],
|
||||
);
|
||||
const active = inGroup
|
||||
? undefined
|
||||
: (bots.find((b) => b.id === botId) ?? (emptyProjectSpace && !botId ? undefined : bots[0]));
|
||||
const activeGroup = groups.find((group) => group.id === groupId);
|
||||
const activeProjectSection = activeGroup?.sectionId
|
||||
? botSections.find((section) => section.id === activeGroup.sectionId)
|
||||
@ -1039,13 +1048,28 @@ export function ShellPage() {
|
||||
return;
|
||||
}
|
||||
const currentGroupId = routeGroupId.current;
|
||||
const currentBotId = routeBotId.current;
|
||||
const refreshHomeSpaceId =
|
||||
navigation.spaces.find((space) => space.isDefault)?.id ?? selectedSpaceId() ?? undefined;
|
||||
const refreshSpaceId = selectedSpaceId() ?? refreshHomeSpaceId;
|
||||
const emptyProject = isEmptyProjectSpace(
|
||||
refreshSpaceId,
|
||||
navigation.spaces,
|
||||
groupList,
|
||||
refreshHomeSpaceId,
|
||||
);
|
||||
if (emptyProject) {
|
||||
if (currentGroupId || (currentBotId && !list.some((bot) => bot.id === currentBotId))) {
|
||||
navigate("/app", { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (currentGroupId) {
|
||||
if (!groupList.some((group) => group.id === currentGroupId)) {
|
||||
navigate(firstThreadRoute(list, groupList), { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const currentBotId = routeBotId.current;
|
||||
if (!currentBotId || !list.some((bot) => bot.id === currentBotId)) {
|
||||
navigate(firstThreadRoute(list, groupList), { replace: true });
|
||||
}
|
||||
@ -1268,6 +1292,21 @@ export function ShellPage() {
|
||||
navigate("/onboarding", { replace: true });
|
||||
return;
|
||||
}
|
||||
const bootstrapHomeSpaceId =
|
||||
bootstrap.spaces.find((space) => space.isDefault)?.id ?? bootstrap.me.spaceId;
|
||||
const bootstrapSpaceId = selectedSpaceId() ?? bootstrap.me.spaceId;
|
||||
const emptyProject = isEmptyProjectSpace(
|
||||
bootstrapSpaceId,
|
||||
bootstrap.spaces,
|
||||
groupList,
|
||||
bootstrapHomeSpaceId,
|
||||
);
|
||||
if (emptyProject) {
|
||||
if (groupId || (botId && !bootstrap.bots.some((bot) => bot.id === botId))) {
|
||||
navigate("/app", { replace: true });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (groupId) {
|
||||
if (!groupList.some((group) => group.id === groupId)) {
|
||||
navigate(firstThreadRoute(bootstrap.bots, groupList), { replace: true });
|
||||
@ -1663,8 +1702,6 @@ export function ShellPage() {
|
||||
[groups],
|
||||
);
|
||||
|
||||
const homeSpaceId = spaces.find((space) => space.isDefault)?.id ?? bootstrapMe?.spaceId;
|
||||
|
||||
const homeSpaceBots = useMemo(() => {
|
||||
if (!homeSpaceId) return bots;
|
||||
const home = spaces.find((space) => space.id === homeSpaceId);
|
||||
@ -1710,10 +1747,7 @@ export function ShellPage() {
|
||||
)
|
||||
: [],
|
||||
);
|
||||
const botsInSpace = sidebarGroups.flatMap((group) =>
|
||||
group.spaceId === space.id ? group.bots.filter((item) => item.kind === "bot") : [],
|
||||
);
|
||||
const projectBots = dedupeSidebarBots(groupsInSpace.length > 0 ? groupsInSpace : botsInSpace);
|
||||
const projectBots = dedupeSidebarBots(groupsInSpace);
|
||||
entries.push({
|
||||
key:
|
||||
projectBots.length > 0 ? `project-space:${space.id}` : `project-space:${space.id}:empty`,
|
||||
@ -1806,6 +1840,14 @@ export function ShellPage() {
|
||||
[bootstrapMe?.spaceId, navigate],
|
||||
);
|
||||
|
||||
const openProjectStart = useCallback(
|
||||
(spaceId: string, flow: "create" | "group") => {
|
||||
setActiveNavPillar("projekte");
|
||||
openSpaceChat(spaceId, `/app?project=start&flow=${flow}`);
|
||||
},
|
||||
[openSpaceChat],
|
||||
);
|
||||
|
||||
const openConversationBot = useCallback(
|
||||
(botId: string) => {
|
||||
setConversationPickerOpen(false);
|
||||
@ -2031,6 +2073,42 @@ export function ShellPage() {
|
||||
void jumpToMessage({ groupId, messageId: projectSessionKey });
|
||||
}, [groupId, inGroup, projectSessionKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!botId && !groupId && emptyProjectSpace && initialBotsLoaded) {
|
||||
setActiveNavPillar("projekte");
|
||||
}
|
||||
}, [botId, emptyProjectSpace, groupId, initialBotsLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (searchParams.get("project") !== "start" || !emptyProjectSpace || !initialBotsLoaded) {
|
||||
return;
|
||||
}
|
||||
setActiveNavPillar("projekte");
|
||||
const flow = searchParams.get("flow");
|
||||
setPanel(
|
||||
flow === "create"
|
||||
? "create"
|
||||
: flow === "group" && homeSpaceBots.length > 0
|
||||
? "create-group"
|
||||
: flow === "group"
|
||||
? "create"
|
||||
: bots.length === 0
|
||||
? "create"
|
||||
: "create-group",
|
||||
);
|
||||
const next = new URLSearchParams(searchParams);
|
||||
next.delete("project");
|
||||
next.delete("flow");
|
||||
setSearchParams(next, { replace: true });
|
||||
}, [
|
||||
bots.length,
|
||||
emptyProjectSpace,
|
||||
homeSpaceBots.length,
|
||||
initialBotsLoaded,
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
]);
|
||||
|
||||
const activeSnapshot = inGroup
|
||||
? snapshot?.groupId === groupId
|
||||
? snapshot
|
||||
@ -3213,7 +3291,7 @@ export function ShellPage() {
|
||||
) : null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!active && !activeGroup && initialBotsLoaded) {
|
||||
if (!active && !activeGroup && initialBotsLoaded && !emptyProjectSpace) {
|
||||
const hauptthread = groups.find((g) => g.name === "Hauptthread" || g.name === "Team Chat");
|
||||
if (hauptthread) {
|
||||
navigate(`/app/g/${hauptthread.id}`);
|
||||
@ -3227,7 +3305,7 @@ export function ShellPage() {
|
||||
.catch(console.error);
|
||||
}
|
||||
}
|
||||
}, [active, activeGroup, initialBotsLoaded, groups, bots, navigate]);
|
||||
}, [active, activeGroup, emptyProjectSpace, initialBotsLoaded, groups, bots, navigate]);
|
||||
|
||||
const userName = session.data?.user.name ?? t`You`;
|
||||
const initials = userName
|
||||
@ -3842,13 +3920,7 @@ export function ShellPage() {
|
||||
className="flex min-w-0 flex-1 items-center justify-between gap-2 rounded-lg pl-8 py-1 text-ui-label font-semibold tracking-wider uppercase text-muted-foreground/60 hover:text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-ring"
|
||||
onClick={() => {
|
||||
if (group.emptySpaceId) {
|
||||
const space = spaces.find(
|
||||
(item) => item.id === group.emptySpaceId,
|
||||
);
|
||||
openSpaceChat(
|
||||
group.emptySpaceId,
|
||||
space?.hasContent ? "/app" : "/onboarding",
|
||||
);
|
||||
openSpaceChat(group.emptySpaceId, "/app");
|
||||
return;
|
||||
}
|
||||
toggleSidebarSection(group.key);
|
||||
@ -3888,7 +3960,7 @@ export function ShellPage() {
|
||||
aria-expanded={group.emptySpaceId ? undefined : !collapsed}
|
||||
aria-label={
|
||||
group.emptySpaceId
|
||||
? t`Open ${group.title}`
|
||||
? t`Select ${group.title}`
|
||||
: collapsed
|
||||
? t`Expand ${group.title}`
|
||||
: t`Collapse ${group.title}`
|
||||
@ -3931,6 +4003,20 @@ export function ShellPage() {
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{!collapsed && group.emptySpaceId ? (
|
||||
<div className="px-3 pb-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="w-full text-ui-label"
|
||||
data-testid={`project-start-${group.spaceId}`}
|
||||
onClick={() => openProjectStart(group.spaceId, "group")}
|
||||
>
|
||||
<Plus className="mr-1 size-icon-sm" />
|
||||
{t`Starten`}
|
||||
</Button>
|
||||
</div>
|
||||
) : null}
|
||||
{!collapsed &&
|
||||
group.bots.map((item) => (
|
||||
<button
|
||||
@ -4394,7 +4480,17 @@ export function ShellPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
{!active && !activeGroup && initialBotsLoaded ? (
|
||||
{!active && !activeGroup && emptyProjectSpace && initialBotsLoaded ? (
|
||||
<div className="grid flex-1 place-items-center">
|
||||
<Button
|
||||
data-testid="project-start-main"
|
||||
onClick={() => currentSpaceId && openProjectStart(currentSpaceId, "create")}
|
||||
>
|
||||
<Plus aria-hidden="true" className="size-icon-md" />
|
||||
{t`Starten`}
|
||||
</Button>
|
||||
</div>
|
||||
) : !active && !activeGroup && initialBotsLoaded ? (
|
||||
<div className="grid flex-1 place-items-center">
|
||||
<Button onClick={() => setShowCreateBotModal(true)}>
|
||||
<Plus aria-hidden="true" className="size-icon-md" />
|
||||
@ -4563,12 +4659,24 @@ export function ShellPage() {
|
||||
data-panel={panel ?? "closed"}
|
||||
style={{
|
||||
width:
|
||||
panel && (active || activeGroup || panel === "create" || panel === "browser")
|
||||
panel &&
|
||||
(active ||
|
||||
activeGroup ||
|
||||
panel === "create" ||
|
||||
panel === "create-group" ||
|
||||
panel === "browser")
|
||||
? rightSidebarWidth
|
||||
: 0,
|
||||
}}
|
||||
className={`absolute inset-y-0 end-0 z-20 flex min-h-0 shrink-0 flex-col overflow-hidden bg-background md:rounded-2xl md:border md:border-white/10 md:bg-[#0A0A0C] md:shadow-2xl md:relative ${
|
||||
panel && (active || activeGroup || panel === "create" || panel === "browser")
|
||||
panel &&
|
||||
(
|
||||
active ||
|
||||
activeGroup ||
|
||||
panel === "create" ||
|
||||
panel === "create-group" ||
|
||||
panel === "browser"
|
||||
)
|
||||
? "max-w-[384px] md:max-w-none opacity-100"
|
||||
: "pointer-events-none w-0 opacity-0 md:opacity-0 md:w-0"
|
||||
} ${!isResizingRight ? "transition-[width,opacity] duration-300 ease-[cubic-bezier(0.16,1,0.3,1)]" : ""}`}
|
||||
@ -4577,7 +4685,7 @@ export function ShellPage() {
|
||||
onPointerDown={startResizingRight}
|
||||
className="absolute -left-2 top-0 bottom-0 w-4 cursor-col-resize z-50 hidden md:block"
|
||||
/>
|
||||
{panel && (active || activeGroup || panel === "create") ? (
|
||||
{panel && (active || activeGroup || panel === "create" || panel === "create-group") ? (
|
||||
<div className="rk-scroll h-full w-full overflow-y-auto px-5 lg:px-7 2xl:px-9 py-[17px]">
|
||||
{panel !== "routine" &&
|
||||
panel !== "create" &&
|
||||
@ -4798,7 +4906,7 @@ export function ShellPage() {
|
||||
) : null}
|
||||
{panel === "create-group" ? (
|
||||
<CreateGroupForm
|
||||
bots={bots}
|
||||
bots={emptyProjectSpace ? (homeSpaceBots as Bot[]) : bots}
|
||||
onCancel={() => setPanel(null)}
|
||||
onCreate={(input) => createGroup(input)}
|
||||
/>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user