Browse Source

Referral improvements. (#934)

pull/935/head
Sebastian Stehle 4 years ago
committed by GitHub
parent
commit
a24b836f95
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      backend/i18n/frontend_en.json
  2. 4
      backend/i18n/frontend_it.json
  3. 4
      backend/i18n/frontend_nl.json
  4. 4
      backend/i18n/frontend_zh.json
  5. 4
      backend/i18n/source/frontend_en.json
  6. 4
      backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs
  7. 8
      backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs
  8. 13
      backend/src/Squidex.Domain.Apps.Entities/Billing/ReferralInfo.cs
  9. 5
      backend/src/Squidex/Areas/Api/Controllers/Plans/AppPlansController.cs
  10. 25
      backend/src/Squidex/Areas/Api/Controllers/Plans/Models/PlansDto.cs
  11. 5
      backend/src/Squidex/Areas/Api/Controllers/Plans/TeamPlansController.cs
  12. 8
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Billing/NoopBillingManagerTests.cs
  13. 7
      frontend/src/app/features/settings/pages/plans/plans-page.component.html
  14. 7
      frontend/src/app/features/teams/pages/plans/plans-page.component.html
  15. 20
      frontend/src/app/features/teams/services/team-plans.service.spec.ts
  16. 19
      frontend/src/app/features/teams/state/team-plans.state.ts
  17. 24
      frontend/src/app/shared/services/plans.service.spec.ts
  18. 16
      frontend/src/app/shared/services/shared.ts
  19. 19
      frontend/src/app/shared/state/plans.state.ts
  20. 1
      frontend/src/app/shell/pages/internal/profile-menu.component.ts

4
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",

4
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",

4
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",

4
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": "已选择",

4
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",

4
backend/src/Squidex.Domain.Apps.Entities/Billing/IBillingManager.cs

@ -18,10 +18,10 @@ namespace Squidex.Domain.Apps.Entities.Billing
Task<Uri?> GetPortalLinkAsync(string userId, ITeamEntity team,
CancellationToken ct = default);
Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, IAppEntity app,
Task<ReferralInfo?> GetReferralInfoAsync(string userId, IAppEntity app,
CancellationToken ct = default);
Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, ITeamEntity team,
Task<ReferralInfo?> GetReferralInfoAsync(string userId, ITeamEntity team,
CancellationToken ct = default);
Task<Uri?> MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId,

8
backend/src/Squidex.Domain.Apps.Entities/Billing/NoopBillingManager.cs

@ -24,16 +24,16 @@ namespace Squidex.Domain.Apps.Entities.Billing
return Task.FromResult<Uri?>(null);
}
public Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, IAppEntity app,
public Task<ReferralInfo?> GetReferralInfoAsync(string userId, IAppEntity app,
CancellationToken ct = default)
{
return Task.FromResult<(string? Code, double AmountEarned)>((null, 0));
return Task.FromResult<ReferralInfo?>(null);
}
public Task<(string? Code, double AmountEarned)> GetReferralCodeAsync(string userId, ITeamEntity team,
public Task<ReferralInfo?> GetReferralInfoAsync(string userId, ITeamEntity team,
CancellationToken ct = default)
{
return Task.FromResult<(string? Code, double AmountEarned)>((null, 0));
return Task.FromResult<ReferralInfo?>(null);
}
public Task<Uri?> MustRedirectToPortalAsync(string userId, IAppEntity app, string? planId,

13
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);
}

5
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());
});

25
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; }
/// <summary>
/// The code for referral managemenent.
/// The referral management.
/// </summary>
public string? ReferralCode { get; set; }
/// <summary>
/// The amount earned.
/// </summary>
public string? ReferralEarned { get; set; }
public ReferralInfo? Referral { get; set; }
/// <summary>
/// The reason why the plan cannot be changed.
/// </summary>
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;

5
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());
});

8
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]

7
frontend/src/app/features/settings/pages/plans/plans-page.component.html

@ -20,12 +20,13 @@
{{ 'plans.managedByTeam' | sqxTranslate }}
</div>
<div class="card card-body mb-4" *ngIf="plansState.referralCode | async">
<div class="card card-body mb-4" *ngIf="plansState.referral | async; let referral">
<h5 class="card-title">Squidex Referal Program</h5>
<sqx-form-hint>
<div [innerHTML]="'plans.referralHint' | sqxTranslate: { code: plansState.snapshot.referralCode } | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralEarned' | sqxTranslate: { amount: plansState.snapshot.referralEarned } | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralHint' | sqxTranslate: referral | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="referral.condition | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralEarned' | sqxTranslate: referral | sqxMarkdown | sqxSafeHtml"></div>
</sqx-form-hint>
</div>

7
frontend/src/app/features/teams/pages/plans/plans-page.component.html

@ -16,12 +16,13 @@
{{ 'plans.allApps' | sqxTranslate }}
</div>
<div class="card card-body mb-4" *ngIf="plansState.referralCode | async">
<div class="card card-body mb-4" *ngIf="plansState.referral | async; let referral">
<h5 class="card-title">Squidex Referal Program</h5>
<sqx-form-hint>
<div [innerHTML]="'plans.referralHint' | sqxTranslate: { code: plansState.snapshot.referralCode } | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralEarned' | sqxTranslate: { amount: plansState.snapshot.referralEarned } | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralHint' | sqxTranslate: referral | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="referral.condition | sqxMarkdown | sqxSafeHtml"></div>
<div [innerHTML]="'plans.referralEarned' | sqxTranslate: referral | sqxMarkdown | sqxSafeHtml"></div>
</sqx-form-hint>
</div>

20
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'),

19
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<Snapshot> {
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<Snapshot> {
planOwner: payload.planOwner,
plans,
portalLink: payload.portalLink,
referralCode: payload.referralCode,
referralEarned: payload.referralEarned,
referral: payload.referral,
version,
}, 'Loading Success');
}),

24
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'),

16
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;

19
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<Snapshot> {
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<Snapshot> {
planOwner: payload.planOwner,
plans,
portalLink: payload.portalLink,
referralCode: payload.referralCode,
referralEarned: payload.referralEarned,
referral: payload.referral,
version,
}, 'Loading Success');
}),

1
frontend/src/app/shell/pages/internal/profile-menu.component.ts

@ -72,7 +72,6 @@ export class ProfileMenuComponent extends StatefulComponent<State> implements On
public changeLanguage(code: string) {
Cookies.replace('.AspNetCore.Culture', `c=${code}|uic=${code}`, 365);
// eslint-disable-next-line no-restricted-globals
location.reload();
}

Loading…
Cancel
Save