mirror of
https://github.com/webstudio-is/webstudio.git
synced 2026-11-09 05:26:24 +09:00
A Free project owner could not switch to Content mode directly, but could open a free Builder share link and use Content mode there. Owners can now use the switch on any plan. Share-link visitors can use it only when the project plan includes Content mode. ## Changes - Keep Content visible in the mode menu. When it is unavailable, disable it and show a Pro badge instead of the shortcut. - Let project owners use Content mode on any plan. - On Free projects, disable Content mode for every share-link permission. A Content-only link opens in Preview; a Builder link still opens in Design. ## Checklist - [x] Keep the Content menu row visible and test its badge and shortcut. - [x] Update the permission rule and tests for Free owners, Free share links, and Pro share links. - [x] Verify a Free Content-only link falls back to Preview mode. - [x] Review Builder docs. The Modes guide already says owners can try Content mode, and the Share links guide says Content-only links require Pro. No copy change is needed. - [x] Run focused tests, Builder typecheck, and lint after the permission change. - [x] Review the non-test diff for redundant code. - [x] Set a paid plan only in E2E tests that open Content mode through an Editor share link. Keep unrelated fixtures on their original plan. - [x] Fix the Design-mode fallback for Free Content-only links and add a regression test. - [x] Confirm required CI checks pass on the latest commit. ## Verification - Builder typecheck: passed. - Oxlint on changed code: passed. - Focused tests: 66 passed across mode, permission, and state suites. The two new Design-entry cases failed before the fix. - Latest-commit CI: Main workflow, Visual regression, Check submodules, and Lint PR passed. Every E2E shard passed. - Manual Storybook screenshots were not run because approval was not received. ## Known limit The Share dialog blocks Content-only links on Free plans. Token creation and update do not enforce that plan limit on the server. This PR controls which Builder mode a share-link visitor can enter; it does not change token issuance. Closes #6618
74 lines
2.9 KiB
TypeScript
74 lines
2.9 KiB
TypeScript
import type { Role } from "@webstudio-is/project";
|
|
import type { AuthPermit } from "@webstudio-is/trpc-interface/index.server";
|
|
import type { PlanFeatures } from "@webstudio-is/plans";
|
|
|
|
/**
|
|
* Human-readable descriptions of what each workspace role can do.
|
|
* Mirrors the role-based permission logic in getPermissions().
|
|
*/
|
|
export const roleDescriptions: Record<Role, string> = {
|
|
viewers: "Can view, copy instances, and clone the project.",
|
|
editors: "Can edit content only — text, images, and predefined components.",
|
|
builders: "Can make any design changes and publish to staging only.",
|
|
administrators: "Can make any design changes and publish to custom domains.",
|
|
};
|
|
|
|
/**
|
|
* Single source of truth for all user permissions. Combines workspace
|
|
* role-based access with subscription plan features.
|
|
*
|
|
* Role-based permission mapping (mirrors backend checks):
|
|
* own → all actions
|
|
* administrators → create, duplicate, rename, tags, transfer
|
|
* builders → create, duplicate, rename, tags
|
|
* editors → rename, tags
|
|
* viewers → open in safe mode only (handled by caller)
|
|
*/
|
|
export const getPermissions = ({
|
|
role,
|
|
planFeatures,
|
|
authPermit,
|
|
workspaces,
|
|
}: {
|
|
role: Role | "own";
|
|
planFeatures: PlanFeatures;
|
|
authPermit?: AuthPermit;
|
|
workspaces: Array<{ role: Role | "own" }>;
|
|
}) => {
|
|
const isOwn = role === "own";
|
|
const isAdmin = role === "administrators";
|
|
const isBuilder = role === "builders" || isAdmin;
|
|
const isEditor = role === "editors" || isBuilder;
|
|
return {
|
|
// Workspace role permissions
|
|
canCreateProject: isOwn || isBuilder,
|
|
canDuplicate: isOwn || isBuilder,
|
|
canRename: isOwn || isEditor,
|
|
canShare: isOwn,
|
|
canDelete: isOwn,
|
|
canTransfer: isOwn || isAdmin,
|
|
canEditTags: isOwn || isEditor,
|
|
canOpenSettings: isOwn,
|
|
// Builders (but not admins/owners) can publish to staging only.
|
|
// Share-link users with "build" permit have the same restriction.
|
|
canPublishToStagingOnly:
|
|
(isBuilder && !isAdmin && !isOwn) || authPermit === "build",
|
|
// Plan feature permissions
|
|
canDownloadAssets: planFeatures.canDownloadAssets,
|
|
canRestoreBackups: planFeatures.canRestoreBackups,
|
|
allowDynamicData: planFeatures.allowDynamicData,
|
|
allowAuth: planFeatures.allowAuth,
|
|
allowContentMode: authPermit === "own" || planFeatures.allowContentMode,
|
|
allowStagingPublish: planFeatures.allowStagingPublish,
|
|
allowAdditionalPermissions: planFeatures.allowAdditionalPermissions,
|
|
maxContactEmailsPerProject: planFeatures.maxContactEmailsPerProject,
|
|
maxDomainsAllowedPerUser: planFeatures.maxDomainsAllowedPerUser,
|
|
maxDailyPublishesPerUser: planFeatures.maxDailyPublishesPerUser,
|
|
maxWorkspaces: planFeatures.maxWorkspaces,
|
|
canInviteMembers: planFeatures.maxWorkspaces > 1,
|
|
canCreateWorkspace:
|
|
workspaces.filter((w) => w.role === "own").length <
|
|
planFeatures.maxWorkspaces,
|
|
};
|
|
};
|