diff --git a/src/frontend/apps/drive/src/features/explorer/hooks/useCreateMenuItems.tsx b/src/frontend/apps/drive/src/features/explorer/hooks/useCreateMenuItems.tsx index 3a7aeb7a..2e857375 100644 --- a/src/frontend/apps/drive/src/features/explorer/hooks/useCreateMenuItems.tsx +++ b/src/frontend/apps/drive/src/features/explorer/hooks/useCreateMenuItems.tsx @@ -14,6 +14,8 @@ import { ExplorerCreateFolderModal } from "../components/modals/ExplorerCreateFo import { useState } from "react"; import { useRouter } from "next/router"; import { isMyFilesRoute } from "@/utils/defaultRoutes"; +import { useEntitlements } from "@/features/entitlement-disclaimers/hooks/useEntitlements"; +import { getCannotUploadReasonDescription } from "@/features/entitlement-disclaimers/disclaimers/CannotUploadDisclaimer"; type UseCreateMenuItemsProps = { includeImport?: boolean; @@ -47,6 +49,18 @@ export const useCreateMenuItems = ({ // it in the current view — no redirect needed. const shouldRedirectToCreated = !canCreateHere && !isOnMyFiles; + // Creating a document writes a real file to storage, so it is gated on the + // same entitlement as an upload. Assume it is allowed while the query is in + // flight: the backend rejects the creation anyway, and greying the entries + // out on every page load would be worse than a rare late disable. + const { data: entitlements } = useEntitlements(); + const canUpload = entitlements?.can_upload.result ?? true; + const cannotUploadReason = canUpload + ? undefined + : (entitlements?.can_upload.message ?? + getCannotUploadReasonDescription(entitlements?.can_upload.reason) ?? + t("entitlements.can_upload.cannot_upload")); + const createFolderModal = useModal(); const [createFileModalType, setCreateFileModalType] = useState(ExplorerCreateFileType.DOC); @@ -97,6 +111,8 @@ export const useCreateMenuItems = ({ }), label: t("explorer.tree.create.file.doc"), callback: () => openCreateFileModal(ExplorerCreateFileType.DOC), + isDisabled: !canUpload, + subText: cannotUploadReason, }, { icon: renderFileIcon({ @@ -107,6 +123,8 @@ export const useCreateMenuItems = ({ }), label: t("explorer.tree.create.file.powerpoint"), callback: () => openCreateFileModal(ExplorerCreateFileType.POWERPOINT), + isDisabled: !canUpload, + subText: cannotUploadReason, }, { icon: renderFileIcon({ @@ -117,6 +135,8 @@ export const useCreateMenuItems = ({ }), label: t("explorer.tree.create.file.calc"), callback: () => openCreateFileModal(ExplorerCreateFileType.CALC), + isDisabled: !canUpload, + subText: cannotUploadReason, }, ); } diff --git a/src/frontend/apps/e2e/__tests__/app-drive/create-file-from-template.spec.ts b/src/frontend/apps/e2e/__tests__/app-drive/create-file-from-template.spec.ts index 13d6c86e..ecf769a1 100644 --- a/src/frontend/apps/e2e/__tests__/app-drive/create-file-from-template.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-drive/create-file-from-template.spec.ts @@ -1,4 +1,4 @@ -import test, { expect } from "@playwright/test"; +import test, { expect, Page } from "@playwright/test"; import { clearDb, login } from "./utils-common"; import { expectRowItem } from "./utils-embedded-grid"; import { createFolderInCurrentFolder } from "./utils-item"; @@ -89,3 +89,48 @@ test.describe("Create file from template in a folder", () => { await expectRowItem(page, "My presentation"); }); }); + +test.describe("Create file from template without the upload entitlement", () => { + const mockEntitlements = async (page: Page) => { + await page.route("**/api/v1.0/entitlements/", async (route) => { + await route.fulfill({ + status: 200, + contentType: "application/json", + body: JSON.stringify({ + can_access: { result: true }, + can_upload: { result: false, reason: "user_quota_exceeded" }, + context: {}, + }), + }); + }); + }; + + test.beforeEach(async ({ page }) => { + await clearDb(); + await login(page, "drive@example.com"); + await mockEntitlements(page); + await page.goto("/"); + }); + + test("disables the document entries and explains why", async ({ page }) => { + await page.getByRole("button", { name: "New" }).click(); + + for (const name of ["New text document", "New spreadsheet", "New slides"]) { + const entry = page.getByRole("menuitem", { name }); + await expect(entry).toBeVisible(); + await expect(entry).toBeDisabled(); + } + + await expect( + page.getByText(/your personal quota has been reached/i).first(), + ).toBeVisible(); + }); + + test("keeps New folder available", async ({ page }) => { + await page.getByRole("button", { name: "New" }).click(); + + await expect( + page.getByRole("menuitem", { name: "New folder" }), + ).toBeEnabled(); + }); +});