🐛 Add fixes for subscription design review (#6751)

* 🐛 Fix from subscription design review

* 📎 Fixes PR feedback
This commit is contained in:
Marina López 2025-06-25 13:41:45 +02:00 committed by GitHub
parent 67ca8ccb22
commit 1f42f032fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 556 additions and 511 deletions

View file

@ -2,11 +2,13 @@
{ {
"~:id": "~uf88e52d7-2b77-81fd-8006-23413fafe56c", "~:id": "~uf88e52d7-2b77-81fd-8006-23413fafe56c",
"~:name": "The Alpaca team", "~:name": "The Alpaca team",
"~:total-editors": 3,
"~:total-members": 3 "~:total-members": 3
}, },
{ {
"~:id": "~u81be1d05-a07b-81d5-8006-3e728bea76fb", "~:id": "~u81be1d05-a07b-81d5-8006-3e728bea76fb",
"~:name": "The Quokka team", "~:name": "The Quokka team",
"~:total-editors": 1,
"~:total-members": 1 "~:total-members": 1
} }
] ]

View file

@ -49,7 +49,7 @@
"~:fullname": "Chewbacca", "~:fullname": "Chewbacca",
"~:is-owner": false, "~:is-owner": false,
"~:modified-at": "~m1713533116365", "~:modified-at": "~m1713533116365",
"~:can-edit": false, "~:can-edit": true,
"~:is-active": true, "~:is-active": true,
"~:id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa", "~:id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa",
"~:profile-id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa", "~:profile-id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa",
@ -77,7 +77,7 @@
"~:fullname": "R2-D2", "~:fullname": "R2-D2",
"~:is-owner": false, "~:is-owner": false,
"~:modified-at": "~m1713533116365", "~:modified-at": "~m1713533116365",
"~:can-edit": false, "~:can-edit": true,
"~:is-active": true, "~:is-active": true,
"~:id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc", "~:id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc",
"~:profile-id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc", "~:profile-id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc",
@ -91,7 +91,7 @@
"~:fullname": "C-3PO", "~:fullname": "C-3PO",
"~:is-owner": false, "~:is-owner": false,
"~:modified-at": "~m1713533116365", "~:modified-at": "~m1713533116365",
"~:can-edit": false, "~:can-edit": true,
"~:is-active": true, "~:is-active": true,
"~:id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd", "~:id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd",
"~:profile-id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd", "~:profile-id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd",
@ -119,7 +119,7 @@
"~:fullname": "Yoda", "~:fullname": "Yoda",
"~:is-owner": false, "~:is-owner": false,
"~:modified-at": "~m1713533116365", "~:modified-at": "~m1713533116365",
"~:can-edit": false, "~:can-edit": true,
"~:is-active": true, "~:is-active": true,
"~:id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef", "~:id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef",
"~:profile-id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef", "~:profile-id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef",

View file

@ -26,5 +26,19 @@
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b", "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
"~:created-at": "~m1713533116365" "~:created-at": "~m1713533116365"
},
{
"~:is-admin": false,
"~:email": "luke@example.com",
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:name": "Luke Skywalker",
"~:fullname": "Luke Skywalker",
"~:is-owner": false,
"~:modified-at": "~m1713533116365",
"~:can-edit": true,
"~:is-active": true,
"~:id": "~u123456789-0000-0000-0000-abcdefabcdef",
"~:profile-id": "~u123456789-0000-0000-0000-abcdefabcdef",
"~:created-at": "~m1713533116365"
} }
] ]

View file

@ -26,5 +26,19 @@
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
"~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b", "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b",
"~:created-at": "~m1713533116365" "~:created-at": "~m1713533116365"
},
{
"~:is-admin": false,
"~:email": "luke@example.com",
"~:team-id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:name": "Luke Skywalker",
"~:fullname": "Luke Skywalker",
"~:is-owner": false,
"~:modified-at": "~m1713533116365",
"~:can-edit": true,
"~:is-active": true,
"~:id": "~u123456789-0000-0000-0000-abcdefabcdef",
"~:profile-id": "~u123456789-0000-0000-0000-abcdefabcdef",
"~:created-at": "~m1713533116365"
} }
] ]

View file

@ -1,52 +0,0 @@
[
{
"~:features": {
"~#set": [
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:owner",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:name": "Default",
"~:modified-at": "~m1713533116375",
"~:id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
"~:created-at": "~m1713533116375",
"~:is-default": true
},
{
"~:features": {
"~#set": [
"layout/grid",
"styles/v2",
"fdata/pointer-map",
"fdata/objects-map",
"components/v2",
"fdata/shape-data-type"
]
},
"~:permissions": {
"~:type": "~:owner",
"~:is-owner": true,
"~:is-admin": true,
"~:can-edit": true
},
"~:subscription": {
"~:type": "unlimited",
"~:status": "paused"
},
"~:name": "Second team",
"~:modified-at": "~m1701164272671",
"~:id": "~udd33ff88-f4e5-8033-8003-8096cc07bdf3",
"~:created-at": "~m1701164272671",
"~:is-default": false
}
]

View file

@ -1,31 +1,30 @@
import { expect } from "@playwright/test"; import { expect } from "@playwright/test";
import { BaseWebSocketPage } from "./BaseWebSocketPage"; import { DashboardPage } from "./DashboardPage";
export class SubscriptionProfilePage extends BaseWebSocketPage { export class SubscriptionProfilePage extends DashboardPage {
static async init(page) { static async init(page) {
await BaseWebSocketPage.initWebSockets(page); await DashboardPage.initWebSockets(page);
await BaseWebSocketPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-owned-teams", "get-owned-teams",
"subscription/get-owned-teams.json", "subscription/get-owned-teams.json",
); );
} }
constructor(page) { constructor(page) {
super(page); super(page);
this.mainHeading = page.getByRole('heading', { name: 'Subscription', level: 2 }); this.mainHeading = page.getByRole("heading", {
name: "Subscription",
level: 2,
});
} }
async goToSubscriptions() { async goToSubscriptions() {
await this.page.goto( await this.page.goto(`#/settings/subscriptions`);
`#/settings/subscriptions`,
);
await expect(this.mainHeading).toBeVisible(); await expect(this.mainHeading).toBeVisible();
} }
} }
export default SubscriptionProfilePage; export default SubscriptionProfilePage;

View file

@ -317,3 +317,5 @@ export class WorkspacePage extends BaseWebSocketPage {
.click(clickOptions); .click(clickOptions);
} }
} }
export default WorkspacePage;

View file

@ -1,14 +1,18 @@
import { test, expect } from "@playwright/test"; import { test, expect } from "@playwright/test";
import DashboardPage from "../pages/DashboardPage"; import DashboardPage from "../pages/DashboardPage";
import { WorkspacePage } from "../pages/WorkspacePage";
import { SubscriptionProfilePage } from "../pages/SubscriptionProfilePage"; test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, [
"enable-subscriptions",
"disable-onboarding",
]);
});
test.describe("Subscriptions: dashboard", () => { test.describe("Subscriptions: dashboard", () => {
test("Team with unlimited subscription has specific icon in menu", async ({ test("Team with unlimited subscription has specific icon in menu", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -45,8 +49,6 @@ test.describe("Subscriptions: dashboard", () => {
test("The Unlimited subscription has its name in the sidebar dropdown", async ({ test("The Unlimited subscription has its name in the sidebar dropdown", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -79,12 +81,10 @@ test.describe("Subscriptions: dashboard", () => {
}); });
}); });
test.describe("Subscriptions: Team members and invitations", () => { test.describe("Subscriptions: team members and invitations", () => {
test("Team settings has susbscription name and no manage subscription link when is member", async ({ test("Team settings has susbscription name and no manage subscription link when is member", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -132,8 +132,6 @@ test.describe("Subscriptions: Team members and invitations", () => {
test("Team settings has susbscription name and manage subscription link when is owner", async ({ test("Team settings has susbscription name and manage subscription link when is owner", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -188,8 +186,6 @@ test.describe("Subscriptions: Team members and invitations", () => {
test("Members tab has warning message when team has more members than subscriptions. Subscribe link is shown for owners.", async ({ test("Members tab has warning message when team has more members than subscriptions. Subscribe link is shown for owners.", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -235,8 +231,6 @@ test.describe("Subscriptions: Team members and invitations", () => {
test("Members tab has warning message when team has more members than subscriptions. Contact to owner is shown for members.", async ({ test("Members tab has warning message when team has more members than subscriptions. Contact to owner is shown for members.", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -282,8 +276,6 @@ test.describe("Subscriptions: Team members and invitations", () => {
test("Members tab has warning message when has professional subscription and more than 8 members.", async ({ test("Members tab has warning message when has professional subscription and more than 8 members.", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -330,11 +322,9 @@ test.describe("Subscriptions: Team members and invitations", () => {
).toBeVisible(); ).toBeVisible();
}); });
test("Invitations tab has warning message when subscription is expired", async ({ test("Invitations tab has warning message when team has more members than subscriptions", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -352,7 +342,7 @@ test.describe("Subscriptions: Team members and invitations", () => {
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-teams", "get-teams",
"subscription/get-teams-unlimited-subscription-expired-owner.json", "subscription/get-teams-unlimited-subscription-owner.json",
); );
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
@ -382,7 +372,7 @@ test.describe("Subscriptions: Team members and invitations", () => {
await expect(page.getByTestId("cta")).toBeVisible(); await expect(page.getByTestId("cta")).toBeVisible();
await expect( await expect(
page.getByText( page.getByText(
"Looks like your team has grown! Your plan includes seats, but you're now using more than that.", "Looks like your team has grown! Your plan includes 2 seats, but you're now using 3",
), ),
).toBeVisible(); ).toBeVisible();
}); });
@ -390,8 +380,6 @@ test.describe("Subscriptions: Team members and invitations", () => {
test("Invitations tab has warning message when has professional subscription and more than 8 members.", async ({ test("Invitations tab has warning message when has professional subscription and more than 8 members.", async ({
page, page,
}) => { }) => {
await DashboardPage.init(page);
await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]);
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-profile", "get-profile",
@ -409,7 +397,7 @@ test.describe("Subscriptions: Team members and invitations", () => {
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
page, page,
"get-teams", "get-teams",
"subscription/get-teams-unlimited-subscription-expired-owner.json", "subscription/get-teams-unlimited-subscription-owner.json",
); );
await DashboardPage.mockRPC( await DashboardPage.mockRPC(
@ -439,274 +427,8 @@ test.describe("Subscriptions: Team members and invitations", () => {
await expect(page.getByTestId("cta")).toBeVisible(); await expect(page.getByTestId("cta")).toBeVisible();
await expect( await expect(
page.getByText( page.getByText(
"Looks like your team has grown! Your plan includes seats, but you're now using more than that.", "Looks like your team has grown! Your plan includes 2 seats, but you're now using 9",
), ),
).toBeVisible(); ).toBeVisible();
}); });
}); });
test.describe("Subscriptions: workspace", () => {
test("Unlimited team should have 'Power up your plan' link in main menu", async ({
page,
}) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-subscriptions"]);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await page.getByRole("button", { name: "Main menu" }).click();
await expect(page.getByText("Power up your plan")).toBeVisible();
});
test("Enterprise team should not have 'Power up your plan' link in main menu", async ({
page,
}) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-subscriptions"]);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await page.getByRole("button", { name: "Main menu" }).click();
await expect(page.getByText("Power up your plan")).not.toBeVisible();
});
test("Professional team should have 7 days autosaved versions", async ({
page,
}) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-subscriptions"]);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 7 days."),
).toBeVisible();
});
test("Unlimited team should have 30 days autosaved versions", async ({
page,
}) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-subscriptions"]);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await WorkspacePage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-one-team.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 30 days."),
).toBeVisible();
});
test("Unlimited team should have 90 days autosaved versions", async ({
page,
}) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, ["enable-subscriptions"]);
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
await WorkspacePage.mockRPC(
page,
"get-teams",
"subscription/get-teams-enterprise-one-team.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 90 days."),
).toBeVisible();
});
});
test.describe("Subscriptions: profile", () => {
test("When subscription is professional there is no manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.init(page);
await SubscriptionProfilePage.mockConfigFlags(page, ["enable-subscriptions"]);
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).not.toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$7")).toBeVisible();
await expect(page.getByText("$950")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).toBeVisible();
});
test("When subscription is unlimited there is manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.init(page);
await SubscriptionProfilePage.mockConfigFlags(page, ["enable-subscriptions"]);
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$0")).toBeVisible();
await expect(page.getByText("$950")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).not.toBeVisible();
await expect(
page.getByRole("button", { name: "Subscribe" }).first(),
).toBeVisible();
});
test("When subscription is enteprise there is manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.init(page);
await SubscriptionProfilePage.mockConfigFlags(page, ["enable-subscriptions"]);
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$0")).toBeVisible();
await expect(page.getByText("$7")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).not.toBeVisible();
await expect(
page.getByRole("button", { name: "Subscribe" }).first(),
).toBeVisible();
});
});

View file

@ -0,0 +1,111 @@
import { test, expect } from "@playwright/test";
import SubscriptionProfilePage from "../pages/SubscriptionProfilePage";
test.beforeEach(async ({ page }) => {
await SubscriptionProfilePage.init(page);
await SubscriptionProfilePage.mockConfigFlags(page, [
"enable-subscriptions",
"disable-onboarding",
]);
});
test.describe("Subscriptions: profile", () => {
test("When subscription is professional there is no manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).not.toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$7")).toBeVisible();
await expect(page.getByText("$950")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).toBeVisible();
});
test("When subscription is unlimited there is manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$0")).toBeVisible();
await expect(page.getByText("$950")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).not.toBeVisible();
await expect(
page.getByRole("button", { name: "Subscribe" }).first(),
).toBeVisible();
});
test("When subscription is enteprise there is manage subscription link", async ({
page,
}) => {
await SubscriptionProfilePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
const subscriptionProfilePage = new SubscriptionProfilePage(page);
await subscriptionProfilePage.goToSubscriptions();
await expect(
page.getByRole("button", { name: "Manage your subscription" }),
).toBeVisible();
await expect(
page.getByRole("heading", { name: "Other Penpot plans", level: 3 }),
).toBeVisible();
await expect(page.getByText("$0")).toBeVisible();
await expect(page.getByText("$7")).toBeVisible();
await expect(
page.getByRole("button", { name: "Try it free for 14 days" }).first(),
).not.toBeVisible();
await expect(
page.getByRole("button", { name: "Subscribe" }).first(),
).toBeVisible();
});
});

View file

@ -0,0 +1,152 @@
import { test, expect } from "@playwright/test";
import WorkspacePage from "../pages/WorkspacePage";
test.beforeEach(async ({ page }) => {
await WorkspacePage.init(page);
await WorkspacePage.mockConfigFlags(page, [
"enable-subscriptions",
"disable-onboarding",
]);
});
test.describe("Subscriptions: workspace", () => {
test("Unlimited team should have 'Power up your plan' link in main menu", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await page.getByRole("button", { name: "Main menu" }).click();
await expect(page.getByText("Power up your plan")).toBeVisible();
});
test("Enterprise team should not have 'Power up your plan' link in main menu", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await page.getByRole("button", { name: "Main menu" }).click();
await expect(page.getByText("Power up your plan")).not.toBeVisible();
});
test("Professional team should have 7 days autosaved versions", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 7 days."),
).toBeVisible();
});
test("Unlimited team should have 30 days autosaved versions", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-unlimited-subscription.json",
);
await WorkspacePage.mockRPC(
page,
"get-teams",
"subscription/get-teams-unlimited-one-team.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 30 days."),
).toBeVisible();
});
test("Unlimited team should have 90 days autosaved versions", async ({
page,
}) => {
const workspacePage = new WorkspacePage(page);
await workspacePage.setupEmptyFile();
await WorkspacePage.mockRPC(
page,
"get-profile",
"subscription/get-profile-enterprise-subscription.json",
);
await WorkspacePage.mockRPC(
page,
"get-teams",
"subscription/get-teams-enterprise-one-team.json",
);
await workspacePage.mockRPC(
"push-audit-events",
"workspace/audit-event-empty.json",
);
await workspacePage.goToWorkspace();
await workspacePage.mockRPC(
"get-file-snapshots?file-id=*",
"workspace/versions-snapshot-1.json",
);
await page.getByLabel("History").click();
await expect(
page.getByText("Autosaved versions will be kept for 90 days."),
).toBeVisible();
});
});

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 63 KiB

View file

@ -132,7 +132,7 @@
[:> team-members-page* {:team team :profile profile}] [:> team-members-page* {:team team :profile profile}]
:dashboard-invitations :dashboard-invitations
[:> team-invitations-page* {:team team}] [:> team-invitations-page* {:team team :profile profile}]
:dashboard-webhooks :dashboard-webhooks
[:> webhooks-page* {:team team}] [:> webhooks-page* {:team team}]

View file

@ -75,10 +75,15 @@
:has-dropdown true}]) :has-dropdown true}])
"enterprise" "enterprise"
[:> cta-power-up* (if subscription-is-trial
{:top-title (tr "subscription.dashboard.power-up.your-subscription") [:> cta-power-up*
:top-description (tr "subscription.dashboard.power-up.enterprise-plan") {:top-title (tr "subscription.dashboard.power-up.your-subscription")
:has-dropdown false}]))) :top-description (tr "subscription.dashboard.power-up.enterprise-trial.top-title")
:has-dropdown false}]
[:> cta-power-up*
{:top-title (tr "subscription.dashboard.power-up.your-subscription")
:top-description (tr "subscription.dashboard.power-up.enterprise-plan")
:has-dropdown false}]))))
(mf/defc team* (mf/defc team*
[{:keys [is-owner team]}] [{:keys [is-owner team]}]
@ -137,12 +142,13 @@
[{:keys [banner-is-expanded team profile]}] [{:keys [banner-is-expanded team profile]}]
(let [subscription (:subscription team) (let [subscription (:subscription team)
subscription-name (:type subscription) subscription-name (:type subscription)
subscription-is-trial (= "trialing" (:status subscription))
is-owner (:is-owner (:permissions team)) is-owner (:is-owner (:permissions team))
email-owner (:email (some #(when (:is-owner %) %) (:members team))) email-owner (:email (some #(when (:is-owner %) %) (:members team)))
mail-to-owner (str "<a href=\"" "mailto:" email-owner "\">" email-owner "</a>") mail-to-owner (str "<a href=\"" "mailto:" email-owner "\">" email-owner "</a>")
go-to-subscription (dm/str (u/join cf/public-uri "#/settings/subscriptions")) go-to-subscription (dm/str (u/join cf/public-uri "#/settings/subscriptions"))
seats (-> profile :props :subscription :quantity)
editors (count (filterv :can-edit (:members team)))
link link
(if is-owner (if is-owner
@ -151,30 +157,25 @@
cta-title cta-title
(cond (cond
(= "professional" subscription-name) (and (= "professional" subscription-name) (>= editors 8))
(tr "subscription.dashboard.cta.professional-plan-designed") (tr "subscription.dashboard.cta.professional-plan-designed")
subscription-is-trial (and (= "unlimited" subscription-name) (< seats editors))
(tr "subscription.dashboard.cta.trial-plan-designed") (tr "subscription.dashboard.cta.unlimited-many-editors" seats editors))
(= "unlimited" subscription-name)
(tr "subscription.dashboard.cta.unlimited-many-editors" (:quantity (:subscription (:props profile)))))
cta-message cta-message
(cond (cond
(and (= "professional" subscription-name) is-owner) (and (= "professional" subscription-name) (>= editors 8) is-owner)
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" link) (tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" link)
(and (= "professional" subscription-name) (not is-owner)) (and (= "professional" subscription-name) (>= editors 8) (not is-owner))
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member" link) (tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-member" link)
(and subscription-is-trial is-owner) (and (= "unlimited" subscription-name) (< seats editors) is-owner)
(tr "subscription.dashboard.cta.upgrade-to-full-access-owner" link) (tr "subscription.dashboard.cta.upgrade-more-seats-owner" link)
(and subscription-is-trial (not is-owner)) (and (= "unlimited" subscription-name) (< seats editors) (not is-owner))
(tr "subscription.dashboard.cta.upgrade-to-full-access-member" link) (tr "subscription.dashboard.cta.upgrade-more-seats-member" link))]
(and (= "unlimited" subscription-name) (not subscription-is-trial))
(tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" link))]
[:> cta* {:class (stl/css-case ::members-cta-full-width banner-is-expanded :members-cta (not banner-is-expanded)) :title cta-title} [:> cta* {:class (stl/css-case ::members-cta-full-width banner-is-expanded :members-cta (not banner-is-expanded)) :title cta-title}
[:> i18n/tr-html* [:> i18n/tr-html*
@ -184,18 +185,24 @@
(defn show-subscription-members-main-banner? (defn show-subscription-members-main-banner?
[team profile] [team profile]
(or (let [seats (-> profile :props :subscription :quantity)
(and (= (:type (:subscription team)) "professional") (>= (count (:members team)) 8)) editors (count (filter :can-edit (:members team)))]
(and (or
(= (:type (:subscription team)) "unlimited") (and (= (:type (:subscription team)) "professional")
(not (= (:status (:subscription team)) "trialing")) (> editors 8))
(>= (count (:members team)) (:quantity (:subscription (:props profile)))) (and
(:is-owner (:permissions team))) (= (:type (:subscription team)) "unlimited")
(= (:status (:subscription team)) "paused"))) (>= editors 8)
(< seats editors)))))
(defn show-subscription-members-small-banner?
[team profile]
(let [seats (-> profile :props :subscription :quantity)
editors (count (filterv :can-edit (:members team)))]
(or
(and (= (:type (:subscription team)) "professional")
(= editors 8))
(and (= (:type (:subscription team)) "unlimited")
(< editors 8)
(< seats editors)))))
(defn show-subscription-invitations-main-banner?
[team]
(or
(and (= (:type (:subscription team)) "professional")
(>= (count (:members team)) 8))
(= (:status (:subscription team)) "paused")))

View file

@ -49,7 +49,7 @@
padding-block-start: var(--sp-m); padding-block-start: var(--sp-m);
} }
.cta-bottom-section .content { .cta-bottom-section .content {
@include t.use-typography("body-small"); @include t.use-typography("body-medium");
@include buttonStyle; @include buttonStyle;
color: var(--color-foreground-secondary); color: var(--color-foreground-secondary);
display: inline-block; display: inline-block;
@ -62,12 +62,12 @@
} }
.cta-title { .cta-title {
@include t.use-typography("title-small"); @include t.use-typography("body-small");
margin-block-end: var(--sp-xs); margin-block-end: var(--sp-xs);
} }
.cta-text { .cta-text {
@include t.use-typography("body-small"); @include t.use-typography("title-small");
} }
.cta-bottom-section .content a { .cta-bottom-section .content a {
@ -96,15 +96,16 @@
} }
.team-text { .team-text {
@include t.use-typography("body-large"); @include t.use-typography("title-medium");
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.manage-subscription-link { .manage-subscription-link {
@include buttonStyle; @include buttonStyle;
@include t.use-typography("body-small"); @include t.use-typography("body-medium");
color: var(--color-accent-tertiary); color: var(--color-accent-tertiary);
display: flex; display: flex;
margin-block-start: -8px;
padding: 0; padding: 0;
} }
@ -112,8 +113,16 @@
@extend .button-icon; @extend .button-icon;
background: var(--color-background-primary); background: var(--color-background-primary);
stroke: var(--color-foreground-secondary); stroke: var(--color-foreground-secondary);
border-radius: var(--sp-xs); border-radius: 6px;
border: $b-1 solid var(--color-foreground-secondary); border: 1.75px solid var(--color-foreground-secondary);
stroke-width: 2.25px;
width: var(--sp-xl);
height: var(--sp-xl);
svg {
block-size: var(--sp-m);
inline-size: var(--sp-m);
}
} }
.menu-item { .menu-item {
@ -136,6 +145,10 @@
margin-block-start: var(--sp-s); margin-block-start: var(--sp-s);
margin-inline-start: $s-68; margin-inline-start: $s-68;
max-width: $s-200; max-width: $s-200;
.cta-title {
line-height: 1.2;
}
} }
.members-cta-full-width { .members-cta-full-width {
@ -148,5 +161,6 @@
a { a {
color: var(--color-accent-primary); color: var(--color-accent-primary);
overflow-wrap: break-word;
} }
} }

View file

@ -25,7 +25,7 @@
[app.main.ui.dashboard.subscription :refer [team* [app.main.ui.dashboard.subscription :refer [team*
members-cta* members-cta*
show-subscription-members-main-banner? show-subscription-members-main-banner?
show-subscription-invitations-main-banner?]] show-subscription-members-small-banner?]]
[app.main.ui.dashboard.team-form] [app.main.ui.dashboard.team-form]
[app.main.ui.ds.foundations.assets.icon :refer [icon*]] [app.main.ui.ds.foundations.assets.icon :refer [icon*]]
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
@ -551,12 +551,11 @@
[:> team-members* [:> team-members*
{:profile profile {:profile profile
:team team}] :team team}]
(when (and (when (and
(contains? cfg/flags :subscriptions) (contains? cfg/flags :subscriptions)
(or (show-subscription-members-small-banner? team profile))
(and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8)) [:> members-cta* {:banner-is-expanded false :team team :profile profile}])]])
(= (:status (:subscription team)) "trialing")))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; INVITATIONS SECTION ;; INVITATIONS SECTION
@ -804,7 +803,7 @@
(mf/defc team-invitations-page* (mf/defc team-invitations-page*
{::mf/props :obj} {::mf/props :obj}
[{:keys [team]}] [{:keys [team profile]}]
(mf/with-effect [team] (mf/with-effect [team]
(dom/set-html-title (dom/set-html-title
@ -821,16 +820,14 @@
:team team}] :team team}]
[:section {:class (stl/css-case [:section {:class (stl/css-case
:dashboard-team-invitations true :dashboard-team-invitations true
:dashboard-top-cta (show-subscription-invitations-main-banner? team))} :dashboard-top-cta (show-subscription-members-main-banner? team profile))}
(when (and (contains? cfg/flags :subscriptions) (when (and (contains? cfg/flags :subscriptions)
(show-subscription-invitations-main-banner? team)) (show-subscription-members-main-banner? team profile))
[:> members-cta* {:banner-is-expanded true :team team}]) [:> members-cta* {:banner-is-expanded true :team team :profile profile}])
[:> invitation-section* {:team team}] [:> invitation-section* {:team team}]
(when (and (contains? cfg/flags :subscriptions) (when (and (contains? cfg/flags :subscriptions)
(or (show-subscription-members-small-banner? team profile))
(and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8)) [:> members-cta* {:banner-is-expanded false :team team :profile profile}])]])
(= (:status (:subscription team)) "trialing")))
[:> members-cta* {:banner-is-expanded false :team team}])]])
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; WEBHOOKS SECTION ;; WEBHOOKS SECTION

View file

@ -107,6 +107,8 @@ $grayish-red: #bfbfbf;
--color-token-border: #{$aqua-400}; --color-token-border: #{$aqua-400};
--color-token-accent: #{$aqua-600}; --color-token-accent: #{$aqua-600};
--color-token-foreground: #{$aqua-800}; --color-token-foreground: #{$aqua-800};
--color-badge-premium: #{$orange-500};
} }
:global(.default) { :global(.default) {
@ -148,4 +150,6 @@ $grayish-red: #bfbfbf;
--color-token-border: #{$violet-700}; --color-token-border: #{$violet-700};
--color-token-accent: #{$violet-600}; --color-token-accent: #{$violet-600};
--color-token-foreground: #{$violet-300}; --color-token-foreground: #{$violet-300};
--color-badge-premium: #{$orange-200};
} }

View file

@ -20,6 +20,7 @@
(def ^:svg-id logo-error-screen "logo-error-screen") (def ^:svg-id logo-error-screen "logo-error-screen")
(def ^:svg-id login-illustration "login-illustration") (def ^:svg-id login-illustration "login-illustration")
(def ^:svg-id logo-subscription "logo-subscription") (def ^:svg-id logo-subscription "logo-subscription")
(def ^:svg-id logo-subscription-light "logo-subscription-light")
(def ^:svg-id marketing-arrows "marketing-arrows") (def ^:svg-id marketing-arrows "marketing-arrows")
(def ^:svg-id marketing-exchange "marketing-exchange") (def ^:svg-id marketing-exchange "marketing-exchange")
(def ^:svg-id marketing-file "marketing-file") (def ^:svg-id marketing-file "marketing-file")

View file

@ -18,4 +18,6 @@
.cta-title { .cta-title {
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
line-height: 1.2;
margin-block-end: var(--sp-m);
} }

View file

@ -19,6 +19,7 @@
(def ^:icon logo-error-screen (icon-xref :logo-error-screen)) (def ^:icon logo-error-screen (icon-xref :logo-error-screen))
(def ^:icon login-illustration (icon-xref :login-illustration)) (def ^:icon login-illustration (icon-xref :login-illustration))
(def ^:icon logo-subscription (icon-xref :logo-subscription)) (def ^:icon logo-subscription (icon-xref :logo-subscription))
(def ^:icon logo-subscription-light (icon-xref :logo-subscription-light))
(def ^:icon brand-openid (icon-xref :brand-openid)) (def ^:icon brand-openid (icon-xref :brand-openid))
(def ^:icon brand-github (icon-xref :brand-github)) (def ^:icon brand-github (icon-xref :brand-github))

View file

@ -2,8 +2,10 @@
(:require-macros [app.main.style :as stl]) (:require-macros [app.main.style :as stl])
(:require (:require
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.math :as mth]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.profile :as du]
[app.main.refs :as refs] [app.main.refs :as refs]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.router :as rt] [app.main.router :as rt]
@ -18,12 +20,23 @@
(mf/defc plan-card* (mf/defc plan-card*
{::mf/props :obj} {::mf/props :obj}
[{:keys [card-title card-title-icon price-value price-period benefits-title benefits cta-text cta-link cta-text-trial cta-link-trial cta-text-with-icon cta-link-with-icon]}] [{:keys [card-title
card-title-icon
price-value price-period
benefits-title benefits
cta-text
cta-link
cta-text-trial
cta-link-trial
cta-text-with-icon
cta-link-with-icon
editors]}]
[:div {:class (stl/css :plan-card)} [:div {:class (stl/css :plan-card)}
[:div {:class (stl/css :plan-card-header)} [:div {:class (stl/css :plan-card-header)}
[:div {:class (stl/css :plan-card-title-container)} [:div {:class (stl/css :plan-card-title-container)}
(when card-title-icon [:span {:class (stl/css :plan-title-icon)} card-title-icon]) (when card-title-icon [:span {:class (stl/css :plan-title-icon)} card-title-icon])
[:h4 {:class (stl/css :plan-card-title)} card-title]] [:h4 {:class (stl/css :plan-card-title)} card-title]
(when editors [:span {:class (stl/css :plan-editors)} (tr "subscription.settings.editors" editors)])]
(when (and price-value price-period) (when (and price-value price-period)
[:div {:class (stl/css :plan-price)} [:div {:class (stl/css :plan-price)}
[:span {:class (stl/css :plan-price-value)} price-value] [:span {:class (stl/css :plan-price-value)} price-value]
@ -45,7 +58,7 @@
::mf/register-as :management-dialog} ::mf/register-as :management-dialog}
[{:keys [subscription-name teams subscribe-to-trial]}] [{:keys [subscription-name teams subscribe-to-trial]}]
(let [min-members* (mf/use-state (or (some->> teams (map :total-members) (apply max)) 1)) (let [min-members* (mf/use-state (or (some->> teams (map :total-editors) (apply max)) 1))
min-members (deref min-members*) min-members (deref min-members*)
formatted-subscription-name (if subscribe-to-trial formatted-subscription-name (if subscribe-to-trial
(if (= subscription-name "unlimited") (if (= subscription-name "unlimited")
@ -75,7 +88,7 @@
returnUrl (js/encodeURIComponent current-href) returnUrl (js/encodeURIComponent current-href)
href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" returnUrl)] href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href)))))) (st/emit! (rt/nav-raw :href href))))))
handle-accept-dialog (mf/use-callback handle-accept-dialog (mf/use-fn
(fn [] (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management" (st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management"
::ev/origin "profile" ::ev/origin "profile"
@ -85,7 +98,7 @@
href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)] href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)]
(st/emit! (rt/nav-raw :href href))) (st/emit! (rt/nav-raw :href href)))
(modal/hide!))) (modal/hide!)))
handle-close-dialog (mf/use-callback handle-close-dialog (mf/use-fn
(fn [] (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"})) (st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"}))
(modal/hide!)))] (modal/hide!)))]
@ -103,7 +116,7 @@
[:ul {:class (stl/css :teams-list)} [:ul {:class (stl/css :teams-list)}
(for [team (js->clj teams :keywordize-keys true)] (for [team (js->clj teams :keywordize-keys true)]
[:li {:key (dm/str (:id team)) :class (stl/css :team-name)} [:li {:key (dm/str (:id team)) :class (stl/css :team-name)}
(:name team) (tr "subscription.settings.management.dialog.members" (:total-members team))])]] (:name team) (tr "subscription.settings.management.dialog.members" (:total-editors team))])]]
[:div {:class (stl/css :modal-text)} [:div {:class (stl/css :modal-text)}
(tr "subscription.settings.management.dialog.no-teams")]) (tr "subscription.settings.management.dialog.no-teams")])
@ -117,8 +130,14 @@
:type "number" :type "number"
:value min-members :value min-members
:min 1 :min 1
:max 1000
:on-change #(let [new-value (js/parseInt (.. % -target -value))] :on-change #(let [new-value (js/parseInt (.. % -target -value))]
(reset! min-members* (if (or (js/isNaN new-value) (zero? new-value)) 1 (max 1 new-value))))}]] (reset! min-members*
(let [v (cond
(or (mth/nan? new-value) (zero? new-value)) 1
(> new-value 1000) 1000
:else (max 1 new-value))]
v)))}]]
[:div {:class (stl/css :editors-cost)} [:div {:class (stl/css :editors-cost)}
[:span {:class (stl/css :modal-text-small)} [:span {:class (stl/css :modal-text-small)}
(tr "subscription.settings.management.dialog.price-month" min-members)] (tr "subscription.settings.management.dialog.price-month" min-members)]
@ -150,7 +169,8 @@
::mf/register-as :subscription-success} ::mf/register-as :subscription-success}
[{:keys [subscription-name]}] [{:keys [subscription-name]}]
(let [handle-close-dialog (mf/use-callback (let [profile (mf/deref refs/profile)
handle-close-dialog (mf/use-fn
(fn [] (fn []
(st/emit! (ptk/event ::ev/event {::ev/name "subscription-success"})) (st/emit! (ptk/event ::ev/event {::ev/name "subscription-success"}))
(modal/hide!)))] (modal/hide!)))]
@ -160,7 +180,9 @@
[:button {:class (stl/css :close-btn) :on-click handle-close-dialog} i/close] [:button {:class (stl/css :close-btn) :on-click handle-close-dialog} i/close]
[:div {:class (stl/css :modal-success-content)} [:div {:class (stl/css :modal-success-content)}
[:div {:class (stl/css :modal-start)} [:div {:class (stl/css :modal-start)}
i/logo-subscription] (if (= "light" (:theme profile))
i/logo-subscription-light
i/logo-subscription)]
[:div {:class (stl/css :modal-end)} [:div {:class (stl/css :modal-end)}
[:div {:class (stl/css :modal-title)} (tr "subscription.settings.sucess.dialog.title" subscription-name)] [:div {:class (stl/css :modal-title)} (tr "subscription.settings.sucess.dialog.title" subscription-name)]
@ -178,6 +200,7 @@
[{:keys [profile]}] [{:keys [profile]}]
(let [route (mf/deref refs/route) (let [route (mf/deref refs/route)
params (:params route) params (:params route)
params-subscription (:subscription (:query params))
show-subscription-success-modal (and (:query params) show-subscription-success-modal (and (:query params)
(or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited") (or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited")
(= (:subscription (:query params)) "subscribed-to-penpot-enterprise"))) (= (:subscription (:query params)) "subscribed-to-penpot-enterprise")))
@ -221,11 +244,19 @@
(mf/with-effect [] (mf/with-effect []
(dom/set-html-title (tr "subscription.labels"))) (dom/set-html-title (tr "subscription.labels")))
(when show-subscription-success-modal (mf/with-effect [show-subscription-success-modal subscription]
(st/emit! (modal/show :subscription-success (when show-subscription-success-modal
{:subscription-name (if (= (:subscription (:query params)) "subscribed-to-penpot-unlimited") (st/emit!
(tr "subscription.settings.unlimited-trial-modal") (modal/show :subscription-success
(tr "subscription.settings.enterprise-trial-modal"))}))) {:subscription-name (if (= params-subscription "subscribed-to-penpot-unlimited")
(tr "subscription.settings.unlimited-trial")
(tr "subscription.settings.enterprise-trial"))})
(du/update-profile-props {:subscription
(assoc subscription :type (if (= params-subscription "subscribed-to-penpot-unlimited")
"unlimited"
"enterprise"))})
(rt/nav :settings-subscription {} {::rt/replace true}))))
[:section {:class (stl/css :dashboard-section)} [:section {:class (stl/css :dashboard-section)}
[:div {:class (stl/css :dashboard-content)} [:div {:class (stl/css :dashboard-content)}
[:h2 {:class (stl/css :title-section)} (tr "subscription.labels")] [:h2 {:class (stl/css :title-section)} (tr "subscription.labels")]
@ -251,7 +282,8 @@
:cta-text (tr "subscription.settings.manage-your-subscription") :cta-text (tr "subscription.settings.manage-your-subscription")
:cta-link go-to-payments :cta-link go-to-payments
:cta-text-trial (tr "subscription.settings.add-payment-to-continue") :cta-text-trial (tr "subscription.settings.add-payment-to-continue")
:cta-link-trial go-to-payments}] :cta-link-trial go-to-payments
:editors (-> profile :props :subscription :quantity)}]
[:> plan-card* {:card-title (tr "subscription.settings.unlimited") [:> plan-card* {:card-title (tr "subscription.settings.unlimited")
:card-title-icon i/character-u :card-title-icon i/character-u
@ -260,17 +292,27 @@
(tr "subscription.settings.unlimited.bill"), (tr "subscription.settings.unlimited.bill"),
(tr "subscription.settings.unlimited.storage")] (tr "subscription.settings.unlimited.storage")]
:cta-text (tr "subscription.settings.manage-your-subscription") :cta-text (tr "subscription.settings.manage-your-subscription")
:cta-link go-to-payments}]) :cta-link go-to-payments
:editors (-> profile :props :subscription :quantity)}])
"enterprise" "enterprise"
[:> plan-card* {:card-title (tr "subscription.settings.enterprise") (if subscription-is-trial
:card-title-icon i/character-e [:> plan-card* {:card-title (tr "subscription.settings.enterprise-trial")
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits") :card-title-icon i/character-e
:benefits [(tr "subscription.settings.enterprise.support"), :benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
(tr "subscription.settings.enterprise.security"), :benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.logs")] (tr "subscription.settings.enterprise.security"),
:cta-text (tr "subscription.settings.manage-your-subscription") (tr "subscription.settings.enterprise.logs")]
:cta-link go-to-payments}]) :cta-text (tr "subscription.settings.manage-your-subscription")
:cta-link go-to-payments}]
[:> plan-card* {:card-title (tr "subscription.settings.enterprise")
:card-title-icon i/character-e
:benefits-title (tr "subscription.settings.benefits.all-professional-benefits")
:benefits [(tr "subscription.settings.enterprise.support"),
(tr "subscription.settings.enterprise.security"),
(tr "subscription.settings.enterprise.logs")]
:cta-text (tr "subscription.settings.manage-your-subscription")
:cta-link go-to-payments}]))
[:div {:class (stl/css :membership-container)} [:div {:class (stl/css :membership-container)}
(when subscription-member [:div {:class (stl/css :membership)} (when subscription-member [:div {:class (stl/css :membership)}

View file

@ -57,7 +57,7 @@
} }
.subscription-member { .subscription-member {
stroke: #fdcd79ff; stroke: var(--color-badge-premium);
} }
.title-section { .title-section {
@ -106,6 +106,13 @@
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
} }
.plan-editors {
@include t.use-typography("body-medium");
align-self: end;
color: var(--color-foreground-primary);
margin-block-end: 2px;
}
.plan-price-period { .plan-price-period {
@include t.use-typography("body-small"); @include t.use-typography("body-small");
color: var(--color-foreground-primary); color: var(--color-foreground-primary);
@ -126,7 +133,7 @@
} }
.cta-button { .cta-button {
@include t.use-typography("body-small"); @include t.use-typography("body-medium");
@include buttonStyle; @include buttonStyle;
color: var(--color-accent-tertiary); color: var(--color-accent-tertiary);
display: flex; display: flex;

View file

@ -27,6 +27,7 @@
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.time :as dt] [app.util.time :as dt]
[cuerdas.core :as str] [cuerdas.core :as str]
[lambdaisland.uri :as u]
[okulary.core :as l] [okulary.core :as l]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -41,6 +42,18 @@
(= subscription-name "enterprise") 90 (= subscription-name "enterprise") 90
:else 7))) :else 7)))
(defn get-versions-warning-subtext
[team]
(let [is-owner? (-> team :permissions :is-owner)
email-owner (:email (some #(when (:is-owner %) %) (:members team)))
go-to-subscription (dm/str (u/join cfg/public-uri "#/settings/subscriptions"))]
(if (contains? cfg/flags :subscriptions)
(if is-owner?
(tr "subscription.workspace.versions.warning.subtext-owner" go-to-subscription)
(tr "subscription.workspace.versions.warning.subtext-member" email-owner email-owner))
(tr "workspace.versions.warning.subtext" "support@penpot.app"))))
(defn group-snapshots (defn group-snapshots
[data] [data]
(->> (concat (->> (concat
@ -369,5 +382,4 @@
[:> i18n/tr-html* [:> i18n/tr-html*
{:tag-name "div" {:tag-name "div"
:class (stl/css :cta) :class (stl/css :cta)
:content (tr "workspace.versions.warning.subtext" :content (get-versions-warning-subtext team)}]]])]))
"mailto:support@penpot.app")}]]])]))

View file

@ -4281,27 +4281,10 @@ msgstr ""
"The Professional plan is designed for teams of up to 8 editors (owner, " "The Professional plan is designed for teams of up to 8 editors (owner, "
"admin, and editor)." "admin, and editor)."
#: src/app/main/ui/dashboard/subscription.cljs:157
msgid "subscription.dashboard.cta.trial-plan-designed"
msgstr ""
"The Unlimited (trial) plan is designed for teams of more than 8 editors "
"(owner, admin, and editor)."
#: src/app/main/ui/dashboard/subscription.cljs:160 #: src/app/main/ui/dashboard/subscription.cljs:160
msgid "subscription.dashboard.cta.unlimited-many-editors" msgid "subscription.dashboard.cta.unlimited-many-editors"
msgstr "" msgstr ""
"Looks like your team has grown! Your plan includes %s seats, but you're now " "Looks like your team has grown! Your plan includes %s seats, but you're now using %s"
"using more than that."
#: src/app/main/ui/dashboard/subscription.cljs:174
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-member"
msgstr "Unlock full access forever. Contact with the team owner: %s"
#: src/app/main/ui/dashboard/subscription.cljs:171
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-owner"
msgstr "Unlock full access forever. [Subscribe now.](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:168 #: src/app/main/ui/dashboard/subscription.cljs:168
#, markdown #, markdown
@ -4315,12 +4298,16 @@ msgstr ""
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner"
msgstr "" msgstr ""
"Get more editors, more storage, and more autosaved versions with the " "Get more editors, more storage, and more autosaved versions with the "
"Unlimited or Enterprise plan. [Subscribe now.](%s)" "Unlimited or Enterprise plan. [Subscribe now.|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:176 #: src/app/main/ui/dashboard/subscription.cljs:176
#, markdown #, markdown
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" msgid "subscription.dashboard.cta.upgrade-more-seats-owner"
msgstr "Please upgrade to match your usage. [Subscribe now.](%s)" msgstr "Please upgrade to match your usage. [Subscribe now.|target:self](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-more-seats-member"
msgstr "Please upgrade to match your usage. Contact with the team owner: %s"
#: src/app/main/ui/dashboard/subscription.cljs:80 #: src/app/main/ui/dashboard/subscription.cljs:80
msgid "subscription.dashboard.power-up.enterprise-plan" msgid "subscription.dashboard.power-up.enterprise-plan"
@ -4336,7 +4323,7 @@ msgstr "Advanced security, activity logs, dedicated support and more."
msgid "subscription.dashboard.power-up.professional.bottom-description" msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr "" msgstr ""
"Get extra editors and storage, file backup and more with the Unlimited " "Get extra editors and storage, file backup and more with the Unlimited "
"plan[Power up](%s)" "plan[Power up|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:59 #: src/app/main/ui/dashboard/subscription.cljs:59
msgid "subscription.dashboard.power-up.professional.top-title" msgid "subscription.dashboard.power-up.professional.top-title"
@ -4350,7 +4337,7 @@ msgstr "Subscribe"
#: src/app/main/ui/dashboard/subscription.cljs:68 #: src/app/main/ui/dashboard/subscription.cljs:68
#, markdown #, markdown
msgid "subscription.dashboard.power-up.trial.bottom-description" msgid "subscription.dashboard.power-up.trial.bottom-description"
msgstr "Enjoying your trial? Unlock full access forever.[Subscribe](%s)" msgstr "Enjoying your trial? Unlock full access forever.[Subscribe|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:62 #: src/app/main/ui/dashboard/subscription.cljs:62
#, unused #, unused
@ -4365,12 +4352,15 @@ msgstr "Unlimited plan (trial)"
msgid "subscription.dashboard.power-up.unlimited-plan" msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Unlimited plan" msgstr "Unlimited plan"
msgid "subscription.dashboard.power-up.enterprise-trial.top-title"
msgstr "Enterprise plan (trial)"
#: src/app/main/ui/dashboard/subscription.cljs:74 #: src/app/main/ui/dashboard/subscription.cljs:74
#, markdown #, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-description" msgid "subscription.dashboard.power-up.unlimited.bottom-description"
msgstr "" msgstr ""
"Get advanced security, activity logs, dedicated support and more. Take a " "Get advanced security, activity logs, dedicated support and more. Take a "
"look to the[Enterprise plan.](%s)" "look to the[Enterprise plan.|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:70 #: src/app/main/ui/dashboard/subscription.cljs:70
#, unused #, unused
@ -4423,10 +4413,6 @@ msgstr "Enterprise"
msgid "subscription.settings.enterprise-trial" msgid "subscription.settings.enterprise-trial"
msgstr "Enterprise (trial)" msgstr "Enterprise (trial)"
#: src/app/main/ui/settings/subscription.cljs:228
msgid "subscription.settings.enterprise-trial-modal"
msgstr "Enterprise trial"
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320 #: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
msgid "subscription.settings.enterprise.logs" msgid "subscription.settings.enterprise.logs"
msgstr "Activity logs" msgstr "Activity logs"
@ -4479,7 +4465,7 @@ msgstr "Apply %s to your teams"
#: src/app/main/ui/settings/subscription.cljs:282 #: src/app/main/ui/settings/subscription.cljs:282
msgid "subscription.settings.member-since" msgid "subscription.settings.member-since"
msgstr "Penpot member since %s" msgstr "Penpot member since: %s"
#: src/app/main/ui/settings/subscription.cljs:295, src/app/main/ui/settings/subscription.cljs:309, src/app/main/ui/settings/subscription.cljs:323 #: src/app/main/ui/settings/subscription.cljs:295, src/app/main/ui/settings/subscription.cljs:309, src/app/main/ui/settings/subscription.cljs:323
msgid "subscription.settings.more-information" msgid "subscription.settings.more-information"
@ -4517,6 +4503,9 @@ msgstr "Unlimited teams of up to 8 editors"
msgid "subscription.settings.section-plan" msgid "subscription.settings.section-plan"
msgstr "Your subscription" msgstr "Your subscription"
msgid "subscription.settings.editors"
msgstr "(x %s editors)"
#: src/app/main/ui/settings/subscription.cljs:145 #: src/app/main/ui/settings/subscription.cljs:145
msgid "subscription.settings.start-trial" msgid "subscription.settings.start-trial"
msgstr "Start free trial" msgstr "Start free trial"
@ -4542,7 +4531,7 @@ msgstr "You are %s!"
#: src/app/main/ui/settings/subscription.cljs:278 #: src/app/main/ui/settings/subscription.cljs:278
#, fuzzy #, fuzzy
msgid "subscription.settings.support-us-since" msgid "subscription.settings.support-us-since"
msgstr "You've been supporting us with this plan since %s" msgstr "You've been supporting us with this plan since: %s"
#: src/app/main/ui/settings/subscription.cljs:307, src/app/main/ui/settings/subscription.cljs:321 #: src/app/main/ui/settings/subscription.cljs:307, src/app/main/ui/settings/subscription.cljs:321
msgid "subscription.settings.try-it-free" msgid "subscription.settings.try-it-free"
@ -4561,10 +4550,6 @@ msgstr "Unlimited"
msgid "subscription.settings.unlimited-trial" msgid "subscription.settings.unlimited-trial"
msgstr "Unlimited (trial)" msgstr "Unlimited (trial)"
#: src/app/main/ui/settings/subscription.cljs:227
msgid "subscription.settings.unlimited-trial-modal"
msgstr "Unlimited trial"
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305 #: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
msgid "subscription.settings.unlimited.bill" msgid "subscription.settings.unlimited.bill"
msgstr "Capped monthly bill" msgstr "Capped monthly bill"
@ -7866,6 +7851,18 @@ msgstr ""
"If you'd like to increase this limit, write to us at " "If you'd like to increase this limit, write to us at "
"[support@penpot.app](%s)" "[support@penpot.app](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.subtext-owner"
msgstr ""
"If you'd like to increase this limit, "
"[upgrade your plan|target:self](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.subtext-member"
msgstr ""
"If you'd like to increase this limit, contact with the team owner: "
"[%s](mailto)"
#: src/app/main/ui/workspace/sidebar/versions.cljs:368 #: src/app/main/ui/workspace/sidebar/versions.cljs:368
msgid "workspace.versions.warning.text" msgid "workspace.versions.warning.text"
msgstr "Autosaved versions will be kept for %s days." msgstr "Autosaved versions will be kept for %s days."

View file

@ -4307,29 +4307,10 @@ msgstr ""
"El plan Professional está diseñado para equipos de hasta 8 editores " "El plan Professional está diseñado para equipos de hasta 8 editores "
"(propietario, administrador y editor)." "(propietario, administrador y editor)."
#: src/app/main/ui/dashboard/subscription.cljs:157
msgid "subscription.dashboard.cta.trial-plan-designed"
msgstr ""
"El plan Unlimited (de prueba) está diseñado para equipos de más de 8 "
"editores (propietario, administrador y editor)."
#: src/app/main/ui/dashboard/subscription.cljs:160 #: src/app/main/ui/dashboard/subscription.cljs:160
msgid "subscription.dashboard.cta.unlimited-many-editors" msgid "subscription.dashboard.cta.unlimited-many-editors"
msgstr "" msgstr ""
"¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora " "¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora estás usando %s"
"estás usando más que eso."
#: src/app/main/ui/dashboard/subscription.cljs:174
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-member"
msgstr ""
"Desbloquea el acceso completo para siempre. Contacta con el propietario del "
"equipo: %s"
#: src/app/main/ui/dashboard/subscription.cljs:171
#, markdown
msgid "subscription.dashboard.cta.upgrade-to-full-access-owner"
msgstr "Desbloquea el acceso completo para siempre. [Suscríbete ahora.](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:168 #: src/app/main/ui/dashboard/subscription.cljs:168
#, markdown #, markdown
@ -4344,12 +4325,17 @@ msgstr ""
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner"
msgstr "" msgstr ""
"Consigue más editores, más almacenamiento y más versiones guardadas " "Consigue más editores, más almacenamiento y más versiones guardadas "
"automáticamente con el plan Unlimited o Enterprise. [Suscríbete ahora.](%s)" "automáticamente con el plan Unlimited o Enterprise. [Suscríbete ahora.|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:176 #: src/app/main/ui/dashboard/subscription.cljs:176
#, markdown #, markdown
msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" msgid "subscription.dashboard.cta.upgrade-more-seats-owner"
msgstr "Por favor, mejóralo para adaptarlo a tu uso. [Suscríbete ahora.](%s)" msgstr "Por favor, mejóralo para adaptarlo a tu uso. [Suscríbete ahora.|target:self](%s)"
#, markdown
msgid "subscription.dashboard.cta.upgrade-more-seats-member"
msgstr "Por favor, mejóralo para adaptarlo a tu uso. Contacta con el "
"propietario del equipo: %s"
#: src/app/main/ui/dashboard/subscription.cljs:80 #: src/app/main/ui/dashboard/subscription.cljs:80
msgid "subscription.dashboard.power-up.enterprise-plan" msgid "subscription.dashboard.power-up.enterprise-plan"
@ -4365,7 +4351,7 @@ msgstr "Seguridad avanzada, registros de actividad, asistencia dedicada y mucho
msgid "subscription.dashboard.power-up.professional.bottom-description" msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr "" msgstr ""
"Consigue editores y almacenamiento adicionales, copias de seguridad de " "Consigue editores y almacenamiento adicionales, copias de seguridad de "
"archivos y mucho más con el Plan Unlimited[Mejóralo](%s)" "archivos y mucho más con el Plan Unlimited[Mejóralo|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:59 #: src/app/main/ui/dashboard/subscription.cljs:59
msgid "subscription.dashboard.power-up.professional.top-title" msgid "subscription.dashboard.power-up.professional.top-title"
@ -4381,7 +4367,7 @@ msgstr "Suscríbete"
msgid "subscription.dashboard.power-up.trial.bottom-description" msgid "subscription.dashboard.power-up.trial.bottom-description"
msgstr "" msgstr ""
"¿Disfrutas de la prueba? Desbloquea el acceso completo para " "¿Disfrutas de la prueba? Desbloquea el acceso completo para "
"siempre.[Suscríbete](%s)" "siempre.[Suscríbete|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:62 #: src/app/main/ui/dashboard/subscription.cljs:62
#, unused #, unused
@ -4394,16 +4380,19 @@ msgstr ""
msgid "subscription.dashboard.power-up.trial.top-title" msgid "subscription.dashboard.power-up.trial.top-title"
msgstr "Plan Unlimited (Prueba)" msgstr "Plan Unlimited (Prueba)"
msgid "subscription.dashboard.power-up.enterprise-trial.top-title"
msgstr "Plan Enterprise (Prueba)"
#: src/app/main/ui/dashboard/subscription.cljs:73 #: src/app/main/ui/dashboard/subscription.cljs:73
msgid "subscription.dashboard.power-up.unlimited-plan" msgid "subscription.dashboard.power-up.unlimited-plan"
msgstr "Plan ilimitado" msgstr "Plan Unlimited"
#: src/app/main/ui/dashboard/subscription.cljs:74 #: src/app/main/ui/dashboard/subscription.cljs:74
#, markdown #, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-description" msgid "subscription.dashboard.power-up.unlimited.bottom-description"
msgstr "" msgstr ""
"Obtenga seguridad avanzada, registros de actividad, asistencia dedicada y " "Obtenga seguridad avanzada, registros de actividad, asistencia dedicada y "
"mucho más. Echa un ojo al[Plan Enterprise](%s)" "mucho más. Echa un ojo al[Plan Enterprise|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:70 #: src/app/main/ui/dashboard/subscription.cljs:70
#, unused #, unused
@ -4453,10 +4442,6 @@ msgstr "Enterprise"
msgid "subscription.settings.enterprise-trial" msgid "subscription.settings.enterprise-trial"
msgstr "Enterprise (prueba)" msgstr "Enterprise (prueba)"
#: src/app/main/ui/settings/subscription.cljs:228
msgid "subscription.settings.enterprise-trial-modal"
msgstr "Enterprise de prueba"
#: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320 #: src/app/main/ui/settings/subscription.cljs:271, src/app/main/ui/settings/subscription.cljs:320
msgid "subscription.settings.enterprise.logs" msgid "subscription.settings.enterprise.logs"
msgstr "Registros de actividad" msgstr "Registros de actividad"
@ -4509,7 +4494,7 @@ msgstr "Aplica %s a tus equipos"
#: src/app/main/ui/settings/subscription.cljs:282 #: src/app/main/ui/settings/subscription.cljs:282
msgid "subscription.settings.member-since" msgid "subscription.settings.member-since"
msgstr "Miembro de penpot desde %s" msgstr "Miembro de penpot desde: %s"
#: src/app/main/ui/settings/subscription.cljs:285 #: src/app/main/ui/settings/subscription.cljs:285
msgid "subscription.settings.other-plans" msgid "subscription.settings.other-plans"
@ -4543,6 +4528,9 @@ msgstr "Equipos ilimitados de hasta 8 redactores"
msgid "subscription.settings.section-plan" msgid "subscription.settings.section-plan"
msgstr "Tu suscripción" msgstr "Tu suscripción"
msgid "subscription.settings.editors"
msgstr "(x %s editores)"
#: src/app/main/ui/settings/subscription.cljs:145 #: src/app/main/ui/settings/subscription.cljs:145
msgid "subscription.settings.start-trial" msgid "subscription.settings.start-trial"
msgstr "Comenzar prueba gratuita" msgstr "Comenzar prueba gratuita"
@ -4567,7 +4555,7 @@ msgstr "Eres %s!"
#: src/app/main/ui/settings/subscription.cljs:278 #: src/app/main/ui/settings/subscription.cljs:278
msgid "subscription.settings.support-us-since" msgid "subscription.settings.support-us-since"
msgstr "Nos has estado apoyando con este plan desde %s" msgstr "Nos has estado apoyando con este plan desde: %s"
#: src/app/main/ui/settings/subscription.cljs:307, src/app/main/ui/settings/subscription.cljs:321 #: src/app/main/ui/settings/subscription.cljs:307, src/app/main/ui/settings/subscription.cljs:321
msgid "subscription.settings.try-it-free" msgid "subscription.settings.try-it-free"
@ -4586,10 +4574,6 @@ msgstr "Unlimited"
msgid "subscription.settings.unlimited-trial" msgid "subscription.settings.unlimited-trial"
msgstr "Unlimited (prueba)" msgstr "Unlimited (prueba)"
#: src/app/main/ui/settings/subscription.cljs:227
msgid "subscription.settings.unlimited-trial-modal"
msgstr "Unlimited de prueba"
#: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305 #: src/app/main/ui/settings/subscription.cljs:249, src/app/main/ui/settings/subscription.cljs:260, src/app/main/ui/settings/subscription.cljs:305
msgid "subscription.settings.unlimited.bill" msgid "subscription.settings.unlimited.bill"
msgstr "Factura mensual limitada" msgstr "Factura mensual limitada"
@ -7829,6 +7813,18 @@ msgstr "Abrir menu de versiones"
msgid "workspace.versions.warning.subtext" msgid "workspace.versions.warning.subtext"
msgstr "Si quieres aumentar este límite, contáctanos en [support@penpot.app](%s)" msgstr "Si quieres aumentar este límite, contáctanos en [support@penpot.app](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.subtext-owner"
msgstr ""
"Si quieres aumentar este límite, "
"[mejora tu plan|target:self](%s)"
#, markdown
msgid "subscription.workspace.versions.warning.subtext-member"
msgstr ""
"Si quieres aumentar este límite, contacta con el propietario del equipo: "
"[%s](mailto)"
#: src/app/main/ui/workspace/sidebar/versions.cljs:368 #: src/app/main/ui/workspace/sidebar/versions.cljs:368
msgid "workspace.versions.warning.text" msgid "workspace.versions.warning.text"
msgstr "Los autoguardados duran %s días." msgstr "Los autoguardados duran %s días."