diff --git a/dashboard/src/data/sponsorships.ts b/dashboard/src/data/sponsorships.ts new file mode 100644 index 00000000..048b70a8 --- /dev/null +++ b/dashboard/src/data/sponsorships.ts @@ -0,0 +1,9 @@ +import { useCall } from "frappe-ui" + +// Only presence matters here: the sidebar drops its Sponsorship item for users with no +// enquiry. No cacheKey — it persists to IndexedDB and would outlive this user's session. +export function useMySponsorships() { + return useCall<{ name: string }[]>({ + url: "/api/v2/method/buzz.api.sponsorships.get_user_sponsorship_inquiries", + }) +} diff --git a/dashboard/src/layouts/ManagerLayout.vue b/dashboard/src/layouts/ManagerLayout.vue index 1b7e9b71..5a8aab69 100644 --- a/dashboard/src/layouts/ManagerLayout.vue +++ b/dashboard/src/layouts/ManagerLayout.vue @@ -2,6 +2,7 @@ import TeamSwitcher from "@/components/TeamSwitcher.vue"; import UserMenu from "@/components/UserMenu.vue"; import { useTeamAccess } from "@/composables/useTeamAccess"; +import { useMySponsorships } from "@/data/sponsorships"; import NotFound from "@/pages/NotFound.vue"; import { DesktopShell, PageHeaderTarget, Sidebar, SidebarItem, SidebarLabel } from "frappe-ui"; import { computed, ref } from "vue"; @@ -16,12 +17,18 @@ const access = useTeamAccess(); // type Boolean, so Vue casts the absent prop to false and the inference never runs. const isActive = (to: string) => route.path === to; -const personalItems = [ +// Sponsorship is only worth a slot once the user has an enquiry to look at, the same +// rule the account tabs follow. +const sponsorships = useMySponsorships(); + +const personalItems = computed(() => [ { label: "My Events", icon: "lucide-calendar-days", to: "/manage/events" }, { label: "My Tickets", icon: "lucide-ticket", to: "/manage/tickets" }, { label: "Talk Proposals", icon: "lucide-file-text", to: "/manage/proposals" }, - { label: "Sponsorship", icon: "lucide-handshake", to: "/manage/sponsorship" }, -]; + ...(sponsorships.data?.length + ? [{ label: "Sponsorship", icon: "lucide-handshake", to: "/manage/sponsorship" }] + : []), +]); // Team-scoped destinations read the active team from data/teams rather than the path, // so they are fixed and need no team loaded to render. diff --git a/e2e/tests/manage-access.spec.ts b/e2e/tests/manage-access.spec.ts index 096d08ab..4692916f 100644 --- a/e2e/tests/manage-access.spec.ts +++ b/e2e/tests/manage-access.spec.ts @@ -11,12 +11,19 @@ test.describe("Manage access", () => { await expect(page.getByRole("heading", { name: "Events", level: 1 })).toBeVisible(); }); - test("routes a sidebar item with no page yet to the placeholder", async ({ page }) => { + test("routes a section with no page yet to the placeholder", async ({ page }) => { + await page.goto("/b/manage/sponsorship"); + + await expect(page.getByText("Work in progress")).toBeVisible({ timeout: 15000 }); + }); + + test("hides Sponsorship from the sidebar without an enquiry", async ({ page }) => { await page.goto("/b/manage/events"); - await page.getByRole("link", { name: "Sponsorship" }).click(); - await expect(page).toHaveURL(/\/b\/manage\/sponsorship$/); - await expect(page.getByText("Work in progress")).toBeVisible(); + await expect(page.getByRole("link", { name: "Talk Proposals" })).toBeVisible({ + timeout: 15000, + }); + await expect(page.getByRole("link", { name: "Sponsorship" })).toHaveCount(0); }); test("shows a 404 for a manage section that does not exist", async ({ page }) => {