diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 925c2687f..8445ac5f9 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -629,8 +629,8 @@ "plans.perMonth": "Per Month", "plans.perYear": "Per Year", "plans.planOwner": "Owner", - "plans.referralEarned": "Amount earned so far: **{amount}**", - "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.\n\nFor the first 12 months they will get a discount of 5% and you get a credit for 10% of the total invoice amount.", + "plans.referralEarned": "Amount earned so far: **{earned}**", + "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.", "plans.refreshTooltip": "Refresh Plans", "plans.reloaded": "Plans reloaded.", "plans.selected": "Selected", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index 949396318..55fc79946 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -629,8 +629,8 @@ "plans.perMonth": "Al mese", "plans.perYear": "all'anno", "plans.planOwner": "Owner", - "plans.referralEarned": "Amount earned so far: **{amount}**", - "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.\n\nFor the first 12 months they will get a discount of 5% and you get a credit for 10% of the total invoice amount.", + "plans.referralEarned": "Amount earned so far: **{earned}**", + "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.", "plans.refreshTooltip": "Aggiorna i piani", "plans.reloaded": "Piano aggiornati.", "plans.selected": "Selezionato", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index dfdf1d585..b09b68907 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -629,8 +629,8 @@ "plans.perMonth": "Per maand", "plans.perYear": "Per jaar", "plans.planOwner": "Owner", - "plans.referralEarned": "Amount earned so far: **{amount}**", - "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.\n\nFor the first 12 months they will get a discount of 5% and you get a credit for 10% of the total invoice amount.", + "plans.referralEarned": "Amount earned so far: **{earned}**", + "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.", "plans.refreshTooltip": "Plannen vernieuwen", "plans.reloaded": "Plans reloaded.", "plans.selected": "Geselecteerd", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index ab45cf9eb..f1c1c7981 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -629,8 +629,8 @@ "plans.perMonth": "每月", "plans.perYear": "每年", "plans.planOwner": "Owner", - "plans.referralEarned": "Amount earned so far: **{amount}**", - "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.\n\nFor the first 12 months they will get a discount of 5% and you get a credit for 10% of the total invoice amount.", + "plans.referralEarned": "Amount earned so far: **{earned}**", + "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.", "plans.refreshTooltip": "刷新计划", "plans.reloaded": "计划重新加载。", "plans.selected": "已选择", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 925c2687f..8445ac5f9 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -629,8 +629,8 @@ "plans.perMonth": "Per Month", "plans.perYear": "Per Year", "plans.planOwner": "Owner", - "plans.referralEarned": "Amount earned so far: **{amount}**", - "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.\n\nFor the first 12 months they will get a discount of 5% and you get a credit for 10% of the total invoice amount.", + "plans.referralEarned": "Amount earned so far: **{earned}**", + "plans.referralHint": "Earn money and just share the following discount code to your friend and family: **{code}**.", "plans.refreshTooltip": "Refresh Plans", "plans.reloaded": "Plans reloaded.", "plans.selected": "Selected", diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs index 6c693e4ec..b44aef49b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs @@ -18,10 +18,10 @@ namespace Squidex.Domain.Apps.Entities.Billing Task GetPortalLinkAsync(string userId, ITeamEntity team, CancellationToken ct = default); - Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, IAppEntity app, + Task GetReferralInfoAsync(string userId, IAppEntity app, CancellationToken ct = default); - Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, ITeamEntity team, + Task GetReferralInfoAsync(string userId, ITeamEntity team, CancellationToken ct = default); Task MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs index df441e4cb..4ad357989 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs @@ -24,16 +24,16 @@ namespace Squidex.Domain.Apps.Entities.Billing return Task.FromResult(null); } - public Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, IAppEntity app, + public Task GetReferralInfoAsync(string userId, IAppEntity app, CancellationToken ct = default) { - return Task.FromResult<(string? Code, double AmountEarned)>((null, 0)); + return Task.FromResult(null); } - public Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, ITeamEntity team, + public Task GetReferralInfoAsync(string userId, ITeamEntity team, CancellationToken ct = default) { - return Task.FromResult<(string? Code, double AmountEarned)>((null, 0)); + return Task.FromResult(null); } public Task MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId, diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/ReferralInfo.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/ReferralInfo.cs new file mode 100644 index 000000000..ebae45dd9 --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/ReferralInfo.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +#pragma warning disable SA1313 // Parameter names should begin with lower-case letter + +namespace Squidex.Domain.Apps.Entities.Billing +{ + public sealed record ReferralInfo(string Code, string Earned, string Condition); +} diff --git a/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs b/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs index 89915dafe..29ba8e1fe 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs @@ -62,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Plans await AsyncHelper.WhenAll( usageGate.GetPlanForAppAsync(App, HttpContext.RequestAborted), billingManager.GetPortalLinkAsync(UserId, App, HttpContext.RequestAborted), - billingManager.GetReferralCodeAsync(UserId, App, HttpContext.RequestAborted)); + billingManager.GetReferralInfoAsync(UserId, App, HttpContext.RequestAborted)); var planOwner = App.Plan?.Owner.Identifier; @@ -88,8 +88,7 @@ namespace Squidex.Areas.Api.Controllers.Plans plans.ToArray(), planOwner, plan.PlanId, - referral.Code, - referral.AmountEarned, + referral, link, GetLocked()); }); diff --git a/backend/src/Squidex/Areas/Api/Controllers/Plans/Models/PlansDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Plans/Models/PlansDto.cs index 397310d02..4076021a1 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Plans/Models/PlansDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Plans/Models/PlansDto.cs @@ -34,42 +34,29 @@ namespace Squidex.Areas.Api.Controllers.Plans.Models public Uri? PortalLink { get; set; } /// - /// The code for referral managemenent. + /// The referral management. /// - public string? ReferralCode { get; set; } - - /// - /// The amount earned. - /// - public string? ReferralEarned { get; set; } + public ReferralInfo? Referral { get; set; } /// /// The reason why the plan cannot be changed. /// public PlansLockedReason Locked { get; set; } - public static PlansDto FromDomain( - Plan[] plans, - string? planOwner, - string? planId, - string? referralCode, - double? referralAmount, - Uri? portalLink, - PlansLockedReason locked) + public static PlansDto FromDomain(Plan[] plans, string? owner, string? planId, ReferralInfo? referral, Uri? link, PlansLockedReason locked) { var result = new PlansDto { Locked = locked, CurrentPlanId = planId, Plans = plans.Select(PlanDto.FromDomain).ToArray(), - PlanOwner = planOwner, - ReferralCode = referralCode, - ReferralEarned = $"{referralAmount ?? 0:0.00} EUR" + PlanOwner = owner, + Referral = referral }; if (locked == PlansLockedReason.None) { - result.PortalLink = portalLink; + result.PortalLink = link; } return result; diff --git a/backend/src/Squidex/Areas/Api/Controllers/Plans/TeamPlansController.cs b/backend/src/Squidex/Areas/Api/Controllers/Plans/TeamPlansController.cs index e8dc6d850..19f0eef01 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Plans/TeamPlansController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Plans/TeamPlansController.cs @@ -62,7 +62,7 @@ namespace Squidex.Areas.Api.Controllers.Plans await AsyncHelper.WhenAll( appUsageGate.GetPlanForTeamAsync(Team, HttpContext.RequestAborted), billingManager.GetPortalLinkAsync(UserId, Team, HttpContext.RequestAborted), - billingManager.GetReferralCodeAsync(UserId, Team, HttpContext.RequestAborted)); + billingManager.GetReferralInfoAsync(UserId, Team, HttpContext.RequestAborted)); PlansLockedReason GetLocked() { @@ -77,8 +77,7 @@ namespace Squidex.Areas.Api.Controllers.Plans return PlansDto.FromDomain( plans.ToArray(), null, plan.PlanId, - referral.Code, - referral.AmountEarned, + referral, link, GetLocked()); }); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs index 8fa69300b..043a30073 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs @@ -58,17 +58,17 @@ namespace Squidex.Domain.Apps.Entities.Billing [Fact] public async Task Should_not_return_referral_code_for_app() { - var actual = await sut.GetReferralCodeAsync(null!, (IAppEntity)null!); + var actual = await sut.GetReferralInfoAsync(null!, (IAppEntity)null!); - Assert.Equal((null, 00), actual); + Assert.Null(actual); } [Fact] public async Task Should_not_return_referral_code_for_team() { - var actual = await sut.GetReferralCodeAsync(null!, (ITeamEntity)null!); + var actual = await sut.GetReferralInfoAsync(null!, (ITeamEntity)null!); - Assert.Equal((null, 0), actual); + Assert.Null(actual); } [Fact] diff --git a/frontend/src/app/features/settings/pages/plans/plans-page.component.html b/frontend/src/app/features/settings/pages/plans/plans-page.component.html index 8ffe4a8b9..f5283a93c 100644 --- a/frontend/src/app/features/settings/pages/plans/plans-page.component.html +++ b/frontend/src/app/features/settings/pages/plans/plans-page.component.html @@ -20,12 +20,13 @@ {{ 'plans.managedByTeam' | sqxTranslate }} -
+
Squidex Referal Program
-
-
+
+
+
diff --git a/frontend/src/app/features/teams/pages/plans/plans-page.component.html b/frontend/src/app/features/teams/pages/plans/plans-page.component.html index b418cbc7c..48e73a754 100644 --- a/frontend/src/app/features/teams/pages/plans/plans-page.component.html +++ b/frontend/src/app/features/teams/pages/plans/plans-page.component.html @@ -16,12 +16,13 @@ {{ 'plans.allApps' | sqxTranslate }}
-
+
Squidex Referal Program
-
-
+
+
+
diff --git a/frontend/src/app/features/teams/services/team-plans.service.spec.ts b/frontend/src/app/features/teams/services/team-plans.service.spec.ts index 5785e1b9d..b2dd2bf9b 100644 --- a/frontend/src/app/features/teams/services/team-plans.service.spec.ts +++ b/frontend/src/app/features/teams/services/team-plans.service.spec.ts @@ -74,6 +74,9 @@ describe('TeamPlansService', () => { maxContributors: 6500, }, ], + referral: { + code: 'CODE', + }, locked: 'ManagedByTeam', }, { headers: { @@ -88,18 +91,27 @@ describe('TeamPlansService', () => { planOwner: '456', plans: [ new PlanDto( - 'free', 'Free', '14 €', + 'free', + 'Free', + '14 €', 'Change for 14 € per month?', - 'free_yearly', '120 €', + 'free_yearly', + '120 €', 'Change for 120 € per year?', 128, 1000, 1500, 2500), new PlanDto( - 'professional', 'Professional', '18 €', + 'professional', + 'Professional', + '18 €', 'Change for 18 € per month?', - 'professional_yearly', '160 €', + 'professional_yearly', + '160 €', 'Change for 160 € per year?', 512, 4000, 5500, 6500), ], + referral: { + code: 'CODE', + } as any, locked: 'ManagedByTeam', }, version: new Version('2'), diff --git a/frontend/src/app/features/teams/state/team-plans.state.ts b/frontend/src/app/features/teams/state/team-plans.state.ts index c9e6e0501..aaba983c2 100644 --- a/frontend/src/app/features/teams/state/team-plans.state.ts +++ b/frontend/src/app/features/teams/state/team-plans.state.ts @@ -9,7 +9,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { finalize, tap } from 'rxjs/operators'; import { TeamPlansService } from '@app/features/teams/internal'; -import { DialogService, LoadingState, PlanDto, PlanLockedReason, shareSubscribed, State, TeamsState, Version } from '@app/shared'; +import { DialogService, LoadingState, PlanDto, PlanLockedReason, ReferralDto, shareSubscribed, State, TeamsState, Version } from '@app/shared'; export interface PlanInfo { // The plan. @@ -32,11 +32,8 @@ interface Snapshot extends LoadingState { // The portal link if available. portalLink?: string; - // The referral code. - referralCode?: string; - - // The amount earned with referrals. - referralEarned?: string; + // The referral info. + referral?: ReferralDto; // The reason why the plan cannot be changed. locked?: PlanLockedReason; @@ -62,11 +59,8 @@ export class TeamPlansState extends State { public locked = this.project(x => x.locked); - public referralCode = - this.project(x => x.referralCode); - - public referralEarned = - this.project(x => x.referralEarned); + public referral = + this.project(x => x.referral); public portalLink = this.project(x => x.portalLink); @@ -112,8 +106,7 @@ export class TeamPlansState extends State { planOwner: payload.planOwner, plans, portalLink: payload.portalLink, - referralCode: payload.referralCode, - referralEarned: payload.referralEarned, + referral: payload.referral, version, }, 'Loading Success'); }), diff --git a/frontend/src/app/shared/services/plans.service.spec.ts b/frontend/src/app/shared/services/plans.service.spec.ts index 64f6ceb9b..50bfbfaa7 100644 --- a/frontend/src/app/shared/services/plans.service.spec.ts +++ b/frontend/src/app/shared/services/plans.service.spec.ts @@ -44,8 +44,6 @@ describe('PlansService', () => { req.flush({ currentPlanId: '123', portalLink: 'link/to/portal', - referralCode: 'CODE', - referralEarned: '100.00 EUR', planOwner: '456', plans: [ { @@ -75,6 +73,9 @@ describe('PlansService', () => { maxContributors: 6500, }, ], + referral: { + code: 'CODE', + }, locked: 'ManagedByTeam', }, { headers: { @@ -86,23 +87,30 @@ describe('PlansService', () => { payload: { currentPlanId: '123', portalLink: 'link/to/portal', - referralCode: 'CODE', - referralEarned: '100.00 EUR', planOwner: '456', plans: [ new PlanDto( - 'free', 'Free', '14 €', + 'free', + 'Free', + '14 €', 'Change for 14 € per month?', - 'free_yearly', '120 €', + 'free_yearly', + '120 €', 'Change for 120 € per year?', 128, 1000, 1500, 2500), new PlanDto( - 'professional', 'Professional', '18 €', + 'professional', + 'Professional', + '18 €', 'Change for 18 € per month?', - 'professional_yearly', '160 €', + 'professional_yearly', + '160 €', 'Change for 160 € per year?', 512, 4000, 5500, 6500), ], + referral: { + code: 'CODE', + } as any, locked: 'ManagedByTeam', }, version: new Version('2'), diff --git a/frontend/src/app/shared/services/shared.ts b/frontend/src/app/shared/services/shared.ts index 7aace4b55..6df30a37f 100644 --- a/frontend/src/app/shared/services/shared.ts +++ b/frontend/src/app/shared/services/shared.ts @@ -92,15 +92,23 @@ export type PlansPayload = Readonly<{ portalLink?: string; // The referral code. - referralCode?: string; - - // The amount earned with referrals. - referralEarned?: string; + referral?: ReferralDto; // The reason why the plan cannot be changed. locked: PlanLockedReason; }>; +export type ReferralDto = Readonly<{ + // The referral code. + code: string; + + // The amount earned. + earned: string; + + // The referral condition. + condition: string; +}>; + export type PlanChangedDto = Readonly<{ // The redirect URI. redirectUri?: string; diff --git a/frontend/src/app/shared/state/plans.state.ts b/frontend/src/app/shared/state/plans.state.ts index 6e8d82e65..50f4f7db0 100644 --- a/frontend/src/app/shared/state/plans.state.ts +++ b/frontend/src/app/shared/state/plans.state.ts @@ -9,7 +9,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { finalize, tap } from 'rxjs/operators'; import { DialogService, LoadingState, shareSubscribed, State, Version } from '@app/framework'; -import { PlanDto, PlanLockedReason, PlansService } from './../services/plans.service'; +import { PlanDto, PlanLockedReason, PlansService, ReferralDto } from './../services/plans.service'; import { AppsState } from './apps.state'; export interface PlanInfo { @@ -33,11 +33,8 @@ interface Snapshot extends LoadingState { // The portal link if available. portalLink?: string; - // The referral code. - referralCode?: string; - - // The amount earned with referrals. - referralEarned?: string; + // The referral info. + referral?: ReferralDto; // The reason why the plan cannot be changed. locked?: PlanLockedReason; @@ -63,11 +60,8 @@ export class PlansState extends State { public locked = this.project(x => x.locked); - public referralCode = - this.project(x => x.referralCode); - - public referralEarned = - this.project(x => x.referralEarned); + public referral = + this.project(x => x.referral); public portalLink = this.project(x => x.portalLink); @@ -117,8 +111,7 @@ export class PlansState extends State { planOwner: payload.planOwner, plans, portalLink: payload.portalLink, - referralCode: payload.referralCode, - referralEarned: payload.referralEarned, + referral: payload.referral, version, }, 'Loading Success'); }), diff --git a/frontend/src/app/shell/pages/internal/profile-menu.component.ts b/frontend/src/app/shell/pages/internal/profile-menu.component.ts index bfd251809..0ac24d4cf 100644 --- a/frontend/src/app/shell/pages/internal/profile-menu.component.ts +++ b/frontend/src/app/shell/pages/internal/profile-menu.component.ts @@ -72,7 +72,6 @@ export class ProfileMenuComponent extends StatefulComponent implements On public changeLanguage(code: string) { Cookies.replace('.AspNetCore.Culture', `c=${code}|uic=${code}`, 365); - // eslint-disable-next-line no-restricted-globals location.reload(); }