diff --git a/frontend/playwright/data/subscription/get-owned-teams.json b/frontend/playwright/data/subscription/get-owned-teams.json index e714a37a7c..7d925f91fc 100644 --- a/frontend/playwright/data/subscription/get-owned-teams.json +++ b/frontend/playwright/data/subscription/get-owned-teams.json @@ -2,11 +2,13 @@ { "~:id": "~uf88e52d7-2b77-81fd-8006-23413fafe56c", "~:name": "The Alpaca team", + "~:total-editors": 3, "~:total-members": 3 }, { "~:id": "~u81be1d05-a07b-81d5-8006-3e728bea76fb", "~:name": "The Quokka team", + "~:total-editors": 1, "~:total-members": 1 } ] \ No newline at end of file diff --git a/frontend/playwright/data/subscription/get-team-members-more-than-8.json b/frontend/playwright/data/subscription/get-team-members-more-than-8.json index 55210eeff2..5679420fb1 100644 --- a/frontend/playwright/data/subscription/get-team-members-more-than-8.json +++ b/frontend/playwright/data/subscription/get-team-members-more-than-8.json @@ -49,7 +49,7 @@ "~:fullname": "Chewbacca", "~:is-owner": false, "~:modified-at": "~m1713533116365", - "~:can-edit": false, + "~:can-edit": true, "~:is-active": true, "~:id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa", "~:profile-id": "~u4b2c3d4e-2345-6789-8002-bcdefabcdefa", @@ -77,7 +77,7 @@ "~:fullname": "R2-D2", "~:is-owner": false, "~:modified-at": "~m1713533116365", - "~:can-edit": false, + "~:can-edit": true, "~:is-active": true, "~:id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc", "~:profile-id": "~u6d4e5f6a-4567-8901-8004-defabcdefabc", @@ -91,7 +91,7 @@ "~:fullname": "C-3PO", "~:is-owner": false, "~:modified-at": "~m1713533116365", - "~:can-edit": false, + "~:can-edit": true, "~:is-active": true, "~:id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd", "~:profile-id": "~u7e5f6a7b-5678-9012-8005-efabcdefabcd", @@ -119,7 +119,7 @@ "~:fullname": "Yoda", "~:is-owner": false, "~:modified-at": "~m1713533116365", - "~:can-edit": false, + "~:can-edit": true, "~:is-active": true, "~:id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef", "~:profile-id": "~u9a7b8c9d-7890-1234-8007-abcdefabcdef", diff --git a/frontend/playwright/data/subscription/get-team-members-subscription-member.json b/frontend/playwright/data/subscription/get-team-members-subscription-member.json index 04237e11f8..6296730aa5 100644 --- a/frontend/playwright/data/subscription/get-team-members-subscription-member.json +++ b/frontend/playwright/data/subscription/get-team-members-subscription-member.json @@ -26,5 +26,19 @@ "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b", "~: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" } ] diff --git a/frontend/playwright/data/subscription/get-team-members-subscription-owner.json b/frontend/playwright/data/subscription/get-team-members-subscription-owner.json index 16bbf41d8d..7e47c4f6aa 100644 --- a/frontend/playwright/data/subscription/get-team-members-subscription-owner.json +++ b/frontend/playwright/data/subscription/get-team-members-subscription-owner.json @@ -26,5 +26,19 @@ "~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b", "~:profile-id": "~uf56647eb-19a7-8115-8003-b6bc939ecd1b", "~: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" } ] diff --git a/frontend/playwright/data/subscription/get-teams-unlimited-subscription-expired-owner.json b/frontend/playwright/data/subscription/get-teams-unlimited-subscription-expired-owner.json deleted file mode 100644 index a74b844fb1..0000000000 --- a/frontend/playwright/data/subscription/get-teams-unlimited-subscription-expired-owner.json +++ /dev/null @@ -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 - } -] diff --git a/frontend/playwright/ui/pages/SubscriptionProfilePage.js b/frontend/playwright/ui/pages/SubscriptionProfilePage.js index d37e625045..304856fdb4 100644 --- a/frontend/playwright/ui/pages/SubscriptionProfilePage.js +++ b/frontend/playwright/ui/pages/SubscriptionProfilePage.js @@ -1,31 +1,30 @@ 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) { - await BaseWebSocketPage.initWebSockets(page); + await DashboardPage.initWebSockets(page); - await BaseWebSocketPage.mockRPC( + await DashboardPage.mockRPC( page, "get-owned-teams", "subscription/get-owned-teams.json", ); - } constructor(page) { super(page); - this.mainHeading = page.getByRole('heading', { name: 'Subscription', level: 2 }); + this.mainHeading = page.getByRole("heading", { + name: "Subscription", + level: 2, + }); } async goToSubscriptions() { - await this.page.goto( - `#/settings/subscriptions`, - ); + await this.page.goto(`#/settings/subscriptions`); await expect(this.mainHeading).toBeVisible(); } - } export default SubscriptionProfilePage; diff --git a/frontend/playwright/ui/pages/WorkspacePage.js b/frontend/playwright/ui/pages/WorkspacePage.js index a57fe35b6d..c85010edc1 100644 --- a/frontend/playwright/ui/pages/WorkspacePage.js +++ b/frontend/playwright/ui/pages/WorkspacePage.js @@ -317,3 +317,5 @@ export class WorkspacePage extends BaseWebSocketPage { .click(clickOptions); } } + +export default WorkspacePage; \ No newline at end of file diff --git a/frontend/playwright/ui/specs/subscriptions.spec.js b/frontend/playwright/ui/specs/subscriptions-dashboard.spec.js similarity index 54% rename from frontend/playwright/ui/specs/subscriptions.spec.js rename to frontend/playwright/ui/specs/subscriptions-dashboard.spec.js index 58742bb1d5..b14abffd48 100644 --- a/frontend/playwright/ui/specs/subscriptions.spec.js +++ b/frontend/playwright/ui/specs/subscriptions-dashboard.spec.js @@ -1,14 +1,18 @@ import { test, expect } from "@playwright/test"; 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("Team with unlimited subscription has specific icon in menu", async ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "get-profile", @@ -45,8 +49,6 @@ test.describe("Subscriptions: dashboard", () => { test("The Unlimited subscription has its name in the sidebar dropdown", async ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "get-profile", @@ -330,11 +322,9 @@ test.describe("Subscriptions: Team members and invitations", () => { ).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, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "get-profile", @@ -352,7 +342,7 @@ test.describe("Subscriptions: Team members and invitations", () => { await DashboardPage.mockRPC( page, "get-teams", - "subscription/get-teams-unlimited-subscription-expired-owner.json", + "subscription/get-teams-unlimited-subscription-owner.json", ); await DashboardPage.mockRPC( @@ -382,7 +372,7 @@ test.describe("Subscriptions: Team members and invitations", () => { await expect(page.getByTestId("cta")).toBeVisible(); await expect( 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(); }); @@ -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 ({ page, }) => { - await DashboardPage.init(page); - await DashboardPage.mockConfigFlags(page, ["enable-subscriptions"]); await DashboardPage.mockRPC( page, "get-profile", @@ -409,7 +397,7 @@ test.describe("Subscriptions: Team members and invitations", () => { await DashboardPage.mockRPC( page, "get-teams", - "subscription/get-teams-unlimited-subscription-expired-owner.json", + "subscription/get-teams-unlimited-subscription-owner.json", ); await DashboardPage.mockRPC( @@ -439,274 +427,8 @@ test.describe("Subscriptions: Team members and invitations", () => { await expect(page.getByTestId("cta")).toBeVisible(); await expect( 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(); }); }); - -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(); - }); -}); diff --git a/frontend/playwright/ui/specs/subscriptions-profile.spec.js b/frontend/playwright/ui/specs/subscriptions-profile.spec.js new file mode 100644 index 0000000000..4f93070fcb --- /dev/null +++ b/frontend/playwright/ui/specs/subscriptions-profile.spec.js @@ -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(); + }); +}); diff --git a/frontend/playwright/ui/specs/subscriptions-workspace.spec.js b/frontend/playwright/ui/specs/subscriptions-workspace.spec.js new file mode 100644 index 0000000000..237f4354b3 --- /dev/null +++ b/frontend/playwright/ui/specs/subscriptions-workspace.spec.js @@ -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(); + }); +}); diff --git a/frontend/resources/images/icons/logo-subscription-light.svg b/frontend/resources/images/icons/logo-subscription-light.svg new file mode 100644 index 0000000000..838ca4b8cd --- /dev/null +++ b/frontend/resources/images/icons/logo-subscription-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 05c523fa29..7ab47029fb 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -132,7 +132,7 @@ [:> team-members-page* {:team team :profile profile}] :dashboard-invitations - [:> team-invitations-page* {:team team}] + [:> team-invitations-page* {:team team :profile profile}] :dashboard-webhooks [:> webhooks-page* {:team team}] diff --git a/frontend/src/app/main/ui/dashboard/subscription.cljs b/frontend/src/app/main/ui/dashboard/subscription.cljs index 62ea6f6f2e..69f8402b04 100644 --- a/frontend/src/app/main/ui/dashboard/subscription.cljs +++ b/frontend/src/app/main/ui/dashboard/subscription.cljs @@ -75,10 +75,15 @@ :has-dropdown true}]) "enterprise" - [:> cta-power-up* - {:top-title (tr "subscription.dashboard.power-up.your-subscription") - :top-description (tr "subscription.dashboard.power-up.enterprise-plan") - :has-dropdown false}]))) + (if subscription-is-trial + [:> cta-power-up* + {:top-title (tr "subscription.dashboard.power-up.your-subscription") + :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* [{:keys [is-owner team]}] @@ -137,12 +142,13 @@ [{:keys [banner-is-expanded team profile]}] (let [subscription (:subscription team) subscription-name (:type subscription) - subscription-is-trial (= "trialing" (:status subscription)) is-owner (:is-owner (:permissions team)) email-owner (:email (some #(when (:is-owner %) %) (:members team))) mail-to-owner (str "" email-owner "") 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 (if is-owner @@ -151,30 +157,25 @@ cta-title (cond - (= "professional" subscription-name) + (and (= "professional" subscription-name) (>= editors 8)) (tr "subscription.dashboard.cta.professional-plan-designed") - subscription-is-trial - (tr "subscription.dashboard.cta.trial-plan-designed") - - (= "unlimited" subscription-name) - (tr "subscription.dashboard.cta.unlimited-many-editors" (:quantity (:subscription (:props profile))))) + (and (= "unlimited" subscription-name) (< seats editors)) + (tr "subscription.dashboard.cta.unlimited-many-editors" seats editors)) cta-message (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) - (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) - (and subscription-is-trial is-owner) - (tr "subscription.dashboard.cta.upgrade-to-full-access-owner" link) + (and (= "unlimited" subscription-name) (< seats editors) is-owner) + (tr "subscription.dashboard.cta.upgrade-more-seats-owner" link) - (and subscription-is-trial (not is-owner)) - (tr "subscription.dashboard.cta.upgrade-to-full-access-member" link) - (and (= "unlimited" subscription-name) (not subscription-is-trial)) - (tr "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" link))] + (and (= "unlimited" subscription-name) (< seats editors) (not is-owner)) + (tr "subscription.dashboard.cta.upgrade-more-seats-member" link))] [:> cta* {:class (stl/css-case ::members-cta-full-width banner-is-expanded :members-cta (not banner-is-expanded)) :title cta-title} [:> i18n/tr-html* @@ -184,18 +185,24 @@ (defn show-subscription-members-main-banner? [team profile] - (or - (and (= (:type (:subscription team)) "professional") (>= (count (:members team)) 8)) - (and - (= (:type (:subscription team)) "unlimited") - (not (= (:status (:subscription team)) "trialing")) - (>= (count (:members team)) (:quantity (:subscription (:props profile)))) - (:is-owner (:permissions team))) - (= (:status (:subscription team)) "paused"))) + (let [seats (-> profile :props :subscription :quantity) + editors (count (filter :can-edit (:members team)))] + (or + (and (= (:type (:subscription team)) "professional") + (> editors 8)) + (and + (= (:type (:subscription team)) "unlimited") + (>= 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"))) diff --git a/frontend/src/app/main/ui/dashboard/subscription.scss b/frontend/src/app/main/ui/dashboard/subscription.scss index 3f203b049d..f864bbf419 100644 --- a/frontend/src/app/main/ui/dashboard/subscription.scss +++ b/frontend/src/app/main/ui/dashboard/subscription.scss @@ -49,7 +49,7 @@ padding-block-start: var(--sp-m); } .cta-bottom-section .content { - @include t.use-typography("body-small"); + @include t.use-typography("body-medium"); @include buttonStyle; color: var(--color-foreground-secondary); display: inline-block; @@ -62,12 +62,12 @@ } .cta-title { - @include t.use-typography("title-small"); + @include t.use-typography("body-small"); margin-block-end: var(--sp-xs); } .cta-text { - @include t.use-typography("body-small"); + @include t.use-typography("title-small"); } .cta-bottom-section .content a { @@ -96,15 +96,16 @@ } .team-text { - @include t.use-typography("body-large"); + @include t.use-typography("title-medium"); color: var(--color-foreground-primary); } .manage-subscription-link { @include buttonStyle; - @include t.use-typography("body-small"); + @include t.use-typography("body-medium"); color: var(--color-accent-tertiary); display: flex; + margin-block-start: -8px; padding: 0; } @@ -112,8 +113,16 @@ @extend .button-icon; background: var(--color-background-primary); stroke: var(--color-foreground-secondary); - border-radius: var(--sp-xs); - border: $b-1 solid var(--color-foreground-secondary); + border-radius: 6px; + 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 { @@ -136,6 +145,10 @@ margin-block-start: var(--sp-s); margin-inline-start: $s-68; max-width: $s-200; + + .cta-title { + line-height: 1.2; + } } .members-cta-full-width { @@ -148,5 +161,6 @@ a { color: var(--color-accent-primary); + overflow-wrap: break-word; } } diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 76ab3cd6f5..b6f0b50ae4 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -25,7 +25,7 @@ [app.main.ui.dashboard.subscription :refer [team* members-cta* show-subscription-members-main-banner? - show-subscription-invitations-main-banner?]] + show-subscription-members-small-banner?]] [app.main.ui.dashboard.team-form] [app.main.ui.ds.foundations.assets.icon :refer [icon*]] [app.main.ui.icons :as i] @@ -551,12 +551,11 @@ [:> team-members* {:profile profile :team team}] + (when (and (contains? cfg/flags :subscriptions) - (or - (and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8)) - (= (:status (:subscription team)) "trialing"))) - [:> members-cta* {:banner-is-expanded false :team team}])]]) + (show-subscription-members-small-banner? team profile)) + [:> members-cta* {:banner-is-expanded false :team team :profile profile}])]]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; INVITATIONS SECTION @@ -804,7 +803,7 @@ (mf/defc team-invitations-page* {::mf/props :obj} - [{:keys [team]}] + [{:keys [team profile]}] (mf/with-effect [team] (dom/set-html-title @@ -821,16 +820,14 @@ :team team}] [:section {:class (stl/css-case :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) - (show-subscription-invitations-main-banner? team)) - [:> members-cta* {:banner-is-expanded true :team team}]) + (show-subscription-members-main-banner? team profile)) + [:> members-cta* {:banner-is-expanded true :team team :profile profile}]) [:> invitation-section* {:team team}] (when (and (contains? cfg/flags :subscriptions) - (or - (and (= (:type (:subscription team)) "professional") (< (count (:members team)) 8)) - (= (:status (:subscription team)) "trialing"))) - [:> members-cta* {:banner-is-expanded false :team team}])]]) + (show-subscription-members-small-banner? team profile)) + [:> members-cta* {:banner-is-expanded false :team team :profile profile}])]]) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; WEBHOOKS SECTION diff --git a/frontend/src/app/main/ui/ds/colors.scss b/frontend/src/app/main/ui/ds/colors.scss index ce535caa48..d3f01a8a16 100644 --- a/frontend/src/app/main/ui/ds/colors.scss +++ b/frontend/src/app/main/ui/ds/colors.scss @@ -107,6 +107,8 @@ $grayish-red: #bfbfbf; --color-token-border: #{$aqua-400}; --color-token-accent: #{$aqua-600}; --color-token-foreground: #{$aqua-800}; + + --color-badge-premium: #{$orange-500}; } :global(.default) { @@ -148,4 +150,6 @@ $grayish-red: #bfbfbf; --color-token-border: #{$violet-700}; --color-token-accent: #{$violet-600}; --color-token-foreground: #{$violet-300}; + + --color-badge-premium: #{$orange-200}; } diff --git a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs index f9e4f37298..a1b5199aa1 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs +++ b/frontend/src/app/main/ui/ds/foundations/assets/raw_svg.cljs @@ -20,6 +20,7 @@ (def ^:svg-id logo-error-screen "logo-error-screen") (def ^:svg-id login-illustration "login-illustration") (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-exchange "marketing-exchange") (def ^:svg-id marketing-file "marketing-file") diff --git a/frontend/src/app/main/ui/ds/product/cta.scss b/frontend/src/app/main/ui/ds/product/cta.scss index f35baf278b..630b8875e4 100644 --- a/frontend/src/app/main/ui/ds/product/cta.scss +++ b/frontend/src/app/main/ui/ds/product/cta.scss @@ -18,4 +18,6 @@ .cta-title { color: var(--color-foreground-primary); + line-height: 1.2; + margin-block-end: var(--sp-m); } diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 37bf248a40..b344a4aebe 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -19,6 +19,7 @@ (def ^:icon logo-error-screen (icon-xref :logo-error-screen)) (def ^:icon login-illustration (icon-xref :login-illustration)) (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-github (icon-xref :brand-github)) diff --git a/frontend/src/app/main/ui/settings/subscription.cljs b/frontend/src/app/main/ui/settings/subscription.cljs index 444da8dc1c..f03477e374 100644 --- a/frontend/src/app/main/ui/settings/subscription.cljs +++ b/frontend/src/app/main/ui/settings/subscription.cljs @@ -2,8 +2,10 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data.macros :as dm] + [app.common.math :as mth] [app.main.data.event :as ev] [app.main.data.modal :as modal] + [app.main.data.profile :as du] [app.main.refs :as refs] [app.main.repo :as rp] [app.main.router :as rt] @@ -18,12 +20,23 @@ (mf/defc plan-card* {::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-header)} [:div {:class (stl/css :plan-card-title-container)} (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) [:div {:class (stl/css :plan-price)} [:span {:class (stl/css :plan-price-value)} price-value] @@ -45,7 +58,7 @@ ::mf/register-as :management-dialog} [{: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*) formatted-subscription-name (if subscribe-to-trial (if (= subscription-name "unlimited") @@ -75,7 +88,7 @@ returnUrl (js/encodeURIComponent current-href) href (dm/str "payments/subscriptions/create?type=enterprise&returnUrl=" returnUrl)] (st/emit! (rt/nav-raw :href href)))))) - handle-accept-dialog (mf/use-callback + handle-accept-dialog (mf/use-fn (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "open-subscription-management" ::ev/origin "profile" @@ -85,7 +98,7 @@ href (dm/str "payments/subscriptions/show?returnUrl=" returnUrl)] (st/emit! (rt/nav-raw :href href))) (modal/hide!))) - handle-close-dialog (mf/use-callback + handle-close-dialog (mf/use-fn (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "close-subscription-modal"})) (modal/hide!)))] @@ -103,7 +116,7 @@ [:ul {:class (stl/css :teams-list)} (for [team (js->clj teams :keywordize-keys true)] [: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)} (tr "subscription.settings.management.dialog.no-teams")]) @@ -117,8 +130,14 @@ :type "number" :value min-members :min 1 + :max 1000 :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)} [:span {:class (stl/css :modal-text-small)} (tr "subscription.settings.management.dialog.price-month" min-members)] @@ -150,7 +169,8 @@ ::mf/register-as :subscription-success} [{:keys [subscription-name]}] - (let [handle-close-dialog (mf/use-callback + (let [profile (mf/deref refs/profile) + handle-close-dialog (mf/use-fn (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "subscription-success"})) (modal/hide!)))] @@ -160,7 +180,9 @@ [: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-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-title)} (tr "subscription.settings.sucess.dialog.title" subscription-name)] @@ -178,6 +200,7 @@ [{:keys [profile]}] (let [route (mf/deref refs/route) params (:params route) + params-subscription (:subscription (:query params)) show-subscription-success-modal (and (:query params) (or (= (:subscription (:query params)) "subscribed-to-penpot-unlimited") (= (:subscription (:query params)) "subscribed-to-penpot-enterprise"))) @@ -221,11 +244,19 @@ (mf/with-effect [] (dom/set-html-title (tr "subscription.labels"))) - (when show-subscription-success-modal - (st/emit! (modal/show :subscription-success - {:subscription-name (if (= (:subscription (:query params)) "subscribed-to-penpot-unlimited") - (tr "subscription.settings.unlimited-trial-modal") - (tr "subscription.settings.enterprise-trial-modal"))}))) + (mf/with-effect [show-subscription-success-modal subscription] + (when show-subscription-success-modal + (st/emit! + (modal/show :subscription-success + {: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)} [:div {:class (stl/css :dashboard-content)} [:h2 {:class (stl/css :title-section)} (tr "subscription.labels")] @@ -251,7 +282,8 @@ :cta-text (tr "subscription.settings.manage-your-subscription") :cta-link go-to-payments :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") :card-title-icon i/character-u @@ -260,17 +292,27 @@ (tr "subscription.settings.unlimited.bill"), (tr "subscription.settings.unlimited.storage")] :cta-text (tr "subscription.settings.manage-your-subscription") - :cta-link go-to-payments}]) + :cta-link go-to-payments + :editors (-> profile :props :subscription :quantity)}]) "enterprise" - [:> 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}]) + (if subscription-is-trial + [:> plan-card* {:card-title (tr "subscription.settings.enterprise-trial") + :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}] + [:> 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)} (when subscription-member [:div {:class (stl/css :membership)} diff --git a/frontend/src/app/main/ui/settings/subscription.scss b/frontend/src/app/main/ui/settings/subscription.scss index d1903f1b88..b077874fd5 100644 --- a/frontend/src/app/main/ui/settings/subscription.scss +++ b/frontend/src/app/main/ui/settings/subscription.scss @@ -57,7 +57,7 @@ } .subscription-member { - stroke: #fdcd79ff; + stroke: var(--color-badge-premium); } .title-section { @@ -106,6 +106,13 @@ 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 { @include t.use-typography("body-small"); color: var(--color-foreground-primary); @@ -126,7 +133,7 @@ } .cta-button { - @include t.use-typography("body-small"); + @include t.use-typography("body-medium"); @include buttonStyle; color: var(--color-accent-tertiary); display: flex; diff --git a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs index 7c0f971890..65b0f13eae 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/versions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/versions.cljs @@ -27,6 +27,7 @@ [app.util.keyboard :as kbd] [app.util.time :as dt] [cuerdas.core :as str] + [lambdaisland.uri :as u] [okulary.core :as l] [rumext.v2 :as mf])) @@ -41,6 +42,18 @@ (= subscription-name "enterprise") 90 :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 [data] (->> (concat @@ -369,5 +382,4 @@ [:> i18n/tr-html* {:tag-name "div" :class (stl/css :cta) - :content (tr "workspace.versions.warning.subtext" - "mailto:support@penpot.app")}]]])])) + :content (get-versions-warning-subtext team)}]]])])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 374ca80637..716d147413 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4281,27 +4281,10 @@ msgstr "" "The Professional plan is designed for teams of up to 8 editors (owner, " "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 msgid "subscription.dashboard.cta.unlimited-many-editors" msgstr "" -"Looks like your team has grown! Your plan includes %s seats, but you're now " -"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)" +"Looks like your team has grown! Your plan includes %s seats, but you're now using %s" #: src/app/main/ui/dashboard/subscription.cljs:168 #, markdown @@ -4315,12 +4298,16 @@ msgstr "" msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" msgstr "" "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 #, markdown -msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" -msgstr "Please upgrade to match your usage. [Subscribe now.](%s)" +msgid "subscription.dashboard.cta.upgrade-more-seats-owner" +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 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" msgstr "" "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 msgid "subscription.dashboard.power-up.professional.top-title" @@ -4350,7 +4337,7 @@ msgstr "Subscribe" #: src/app/main/ui/dashboard/subscription.cljs:68 #, markdown 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 #, unused @@ -4365,12 +4352,15 @@ msgstr "Unlimited plan (trial)" msgid "subscription.dashboard.power-up.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 #, markdown msgid "subscription.dashboard.power-up.unlimited.bottom-description" msgstr "" "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 #, unused @@ -4423,10 +4413,6 @@ msgstr "Enterprise" msgid "subscription.settings.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 msgid "subscription.settings.enterprise.logs" msgstr "Activity logs" @@ -4479,7 +4465,7 @@ msgstr "Apply %s to your teams" #: src/app/main/ui/settings/subscription.cljs:282 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 msgid "subscription.settings.more-information" @@ -4517,6 +4503,9 @@ msgstr "Unlimited teams of up to 8 editors" msgid "subscription.settings.section-plan" msgstr "Your subscription" +msgid "subscription.settings.editors" +msgstr "(x %s editors)" + #: src/app/main/ui/settings/subscription.cljs:145 msgid "subscription.settings.start-trial" msgstr "Start free trial" @@ -4542,7 +4531,7 @@ msgstr "You are %s!" #: src/app/main/ui/settings/subscription.cljs:278 #, fuzzy 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 msgid "subscription.settings.try-it-free" @@ -4561,10 +4550,6 @@ msgstr "Unlimited" msgid "subscription.settings.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 msgid "subscription.settings.unlimited.bill" msgstr "Capped monthly bill" @@ -7866,6 +7851,18 @@ msgstr "" "If you'd like to increase this limit, write to us at " "[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 msgid "workspace.versions.warning.text" msgstr "Autosaved versions will be kept for %s days." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 4c1f4486ee..a750e23e5c 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4307,29 +4307,10 @@ msgstr "" "El plan Professional está diseñado para equipos de hasta 8 editores " "(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 msgid "subscription.dashboard.cta.unlimited-many-editors" msgstr "" -"¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora " -"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)" +"¡Parece que tu equipo ha crecido! Tu plan incluye %s asientos, pero ahora estás usando %s" #: src/app/main/ui/dashboard/subscription.cljs:168 #, markdown @@ -4344,12 +4325,17 @@ msgstr "" msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner" msgstr "" "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 #, markdown -msgid "subscription.dashboard.cta.upgrade-to-unlimited-enterprise-owner-more-seats" -msgstr "Por favor, mejóralo para adaptarlo a tu uso. [Suscríbete ahora.](%s)" +msgid "subscription.dashboard.cta.upgrade-more-seats-owner" +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 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" msgstr "" "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 msgid "subscription.dashboard.power-up.professional.top-title" @@ -4381,7 +4367,7 @@ msgstr "Suscríbete" msgid "subscription.dashboard.power-up.trial.bottom-description" msgstr "" "¿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 #, unused @@ -4394,16 +4380,19 @@ msgstr "" msgid "subscription.dashboard.power-up.trial.top-title" 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 msgid "subscription.dashboard.power-up.unlimited-plan" -msgstr "Plan ilimitado" +msgstr "Plan Unlimited" #: src/app/main/ui/dashboard/subscription.cljs:74 #, markdown msgid "subscription.dashboard.power-up.unlimited.bottom-description" msgstr "" "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 #, unused @@ -4453,10 +4442,6 @@ msgstr "Enterprise" msgid "subscription.settings.enterprise-trial" 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 msgid "subscription.settings.enterprise.logs" msgstr "Registros de actividad" @@ -4509,7 +4494,7 @@ msgstr "Aplica %s a tus equipos" #: src/app/main/ui/settings/subscription.cljs:282 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 msgid "subscription.settings.other-plans" @@ -4543,6 +4528,9 @@ msgstr "Equipos ilimitados de hasta 8 redactores" msgid "subscription.settings.section-plan" msgstr "Tu suscripción" +msgid "subscription.settings.editors" +msgstr "(x %s editores)" + #: src/app/main/ui/settings/subscription.cljs:145 msgid "subscription.settings.start-trial" msgstr "Comenzar prueba gratuita" @@ -4567,7 +4555,7 @@ msgstr "Eres %s!" #: src/app/main/ui/settings/subscription.cljs:278 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 msgid "subscription.settings.try-it-free" @@ -4586,10 +4574,6 @@ msgstr "Unlimited" msgid "subscription.settings.unlimited-trial" 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 msgid "subscription.settings.unlimited.bill" msgstr "Factura mensual limitada" @@ -7829,6 +7813,18 @@ msgstr "Abrir menu de versiones" msgid "workspace.versions.warning.subtext" 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 msgid "workspace.versions.warning.text" msgstr "Los autoguardados duran %s días."