Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions apps/builder/app/builder/features/topbar/publish.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import stripIndent from "strip-indent";
import { $isPublishDialogOpen } from "../../shared/nano-states";
import { validateProjectDomain, type Project } from "@webstudio-is/project";
import {
$authPermit,
$authTokenPermissions,
$project,
$publishedOrigin,
$userPlanFeatures,
Expand Down Expand Up @@ -837,16 +837,16 @@ type PublishProps = {

export const PublishButton = ({ projectId }: PublishProps) => {
const isPublishDialogOpen = useStore($isPublishDialogOpen);
const authPermit = useStore($authPermit);
const authTokenPermissions = useStore($authTokenPermissions);
const [dialogContentType, setDialogContentType] = useState<
"publish" | "export"
>("publish");

const isPublishEnabled = authPermit === "own" || authPermit === "admin";
const isPublishEnabled = authTokenPermissions.canPublish;

const tooltipContent = isPublishEnabled
? undefined
: "Only owner or admin can publish projects";
: "Only the owner, an admin, or content editors with publish permissions can publish projects";

const handleExportClick = () => {
setDialogContentType("export");
Expand Down
1 change: 1 addition & 0 deletions apps/builder/app/shared/nano-states/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ export const $authPermit = atom<AuthPermit>("view");
export const $authTokenPermissions = atom<TokenPermissions>({
canClone: true,
canCopy: true,
canPublish: false,
});

export const $authToken = atom<string | undefined>(undefined);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,23 @@ const initialLinks: Array<LinkOptions> = [
relation: "viewers",
canClone: false,
canCopy: false,
canPublish: false,
},
{
token: crypto.randomUUID(),
name: "View and Edit",
relation: "editors",
canClone: false,
canCopy: false,
canPublish: false,
},
{
token: crypto.randomUUID(),
name: "Build",
relation: "builders",
canClone: false,
canCopy: false,
canPublish: false,
},
];

Expand Down Expand Up @@ -63,6 +66,7 @@ const useShareProject = (
relation: "viewers",
canClone: false,
canCopy: false,
canPublish: false,
},
]);
};
Expand Down
98 changes: 67 additions & 31 deletions apps/builder/app/shared/share-project/share-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type MenuProps = {
};

const Menu = ({ name, hasProPlan, value, onChange, onDelete }: MenuProps) => {
const ids = useIds(["name", "canClone", "canCopy"]);
const ids = useIds(["name", "canClone", "canCopy", "canPublish"]);
const [isOpen, setIsOpen] = useState(false);
const [customLinkName, setCustomLinkName] = useState<string>(name);

Expand Down Expand Up @@ -234,36 +234,71 @@ const Menu = ({ name, hasProPlan, value, onChange, onDelete }: MenuProps) => {
</Grid>

{isFeatureEnabled("contentEditableMode") && (
<Permission
disabled={hasProPlan !== true}
onCheckedChange={handleCheckedChange("editors")}
checked={value.relation === "editors"}
title="Content"
info={
<Flex direction="column">
Recipients can edit content only, such as text, images, and
predefined components.
{hasProPlan !== true && (
<>
<br />
<br />
Upgrade to a Pro account to share with Content Edit
permissions.
<br /> <br />
<Link
className={buttonStyle({ color: "gradient" })}
color="contrast"
underline="none"
href="https://webstudio.is/pricing"
target="_blank"
>
Upgrade
</Link>
</>
)}
</Flex>
}
/>
<>
<Permission
disabled={hasProPlan !== true}
onCheckedChange={handleCheckedChange("editors")}
checked={value.relation === "editors"}
title="Content"
info={
<Flex direction="column">
Recipients can edit content only, such as text, images, and
predefined components.
{hasProPlan !== true && (
<>
<br />
<br />
Upgrade to a Pro account to share with Content Edit
permissions.
<br /> <br />
<Link
className={buttonStyle({ color: "gradient" })}
color="contrast"
underline="none"
href="https://webstudio.is/pricing"
target="_blank"
>
Upgrade
</Link>
</>
)}
</Flex>
}
/>
<Grid
css={{
ml: theme.spacing[6],
}}
>
<Grid
gap={1}
flow={"column"}
css={{
alignItems: "center",
justifyContent: "start",
}}
>
<Checkbox
disabled={
hasProPlan !== true || value.relation !== "editors"
}
checked={value.canPublish}
onCheckedChange={(canPublish) => {
onChange({ ...value, canPublish: Boolean(canPublish) });
}}
id={ids.canPublish}
/>
<Label
htmlFor={ids.canPublish}
disabled={
hasProPlan !== true || value.relation !== "editors"
}
>
Can publish
</Label>
</Grid>
</Grid>
</>
)}

<Permission
Expand Down Expand Up @@ -336,6 +371,7 @@ export type LinkOptions = {
relation: Relation;
canCopy: boolean;
canClone: boolean;
canPublish: boolean;
};

type SharedLinkItemType = {
Expand Down
36 changes: 33 additions & 3 deletions packages/authorization-token/src/db/authorization-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,42 @@ type AuthorizationToken =
const applyTokenPermissions = (
token: AuthorizationToken
): AuthorizationToken => {
let result = token;

// @todo: fix this on SQL level
if (token.relation !== "viewers") {
return {
...token,
result = {
...result,
canClone: true,
canCopy: true,
};
}

return token;
// @todo: fix this on SQL level
if (token.relation === "viewers") {
result = {
...result,
canPublish: false,
};
}

// @todo: fix this on SQL level
if (token.relation === "builders") {
result = {
...result,
canPublish: false,
};
}

// @todo: fix this on SQL level
if (token.relation === "administrators") {
result = {
...result,
canPublish: true,
};
}

return result;
};

export const findMany = async (
Expand Down Expand Up @@ -55,6 +82,7 @@ export const findMany = async (
export const tokenDefaultPermissions = {
canClone: true,
canCopy: true,
canPublish: true,
};

export type TokenPermissions = typeof tokenDefaultPermissions;
Expand Down Expand Up @@ -89,6 +117,7 @@ export const getTokenPermissions = async (
return {
canClone: dbToken.canClone,
canCopy: dbToken.canCopy,
canPublish: dbToken.canPublish,
};
};

Expand Down Expand Up @@ -168,6 +197,7 @@ export const update = async (
relation: props.relation,
canClone: props.canClone,
canCopy: props.canCopy,
canPublish: props.canPublish,
})
.eq("projectId", projectId)
.eq("token", props.token)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export const authorizationTokenRouter = router({
relation: TokenProjectRelation,
canClone: z.boolean(),
canCopy: z.boolean(),
canPublish: z.boolean(),
})
)
.mutation(async ({ input, ctx }) => {
Expand All @@ -77,6 +78,7 @@ export const authorizationTokenRouter = router({
token: input.token,
name: input.name,
relation: input.relation,
canPublish: input.canPublish,
canClone: input.canClone,
canCopy: input.canCopy,
},
Expand Down
Loading
Loading