+
diff --git a/src/Squidex/app/features/settings/pages/plans/plans-page.component.ts b/src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
index 793c6674b..062180288 100644
--- a/src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
+++ b/src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
@@ -5,18 +5,14 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { Subscription } from 'rxjs';
import {
ApiUrlConfig,
- AppPlansDto,
AppsState,
- AuthService,
- ChangePlanDto,
- DialogService,
- PlansService
+ PlansState,
+ PlanDto
} from '@app/shared';
@Component({
@@ -24,75 +20,37 @@ import {
styleUrls: ['./plans-page.component.scss'],
templateUrl: './plans-page.component.html'
})
-export class PlansPageComponent implements OnDestroy, OnInit {
- private queryParamsSubscription: Subscription;
+export class PlansPageComponent implements OnInit {
private overridePlanId: string;
public portalUrl = this.apiUrl.buildUrl('/portal/');
- public plans: AppPlansDto;
- public planOwned = false;
-
- public isDisabled = false;
-
constructor(
public readonly appsState: AppsState,
+ public readonly plansState: PlansState,
private readonly apiUrl: ApiUrlConfig,
- private readonly authState: AuthService,
- private readonly dialogs: DialogService,
- private readonly plansService: PlansService,
private readonly route: ActivatedRoute
) {
}
- public ngOnDestroy() {
- this.queryParamsSubscription.unsubscribe();
- }
-
public ngOnInit() {
- this.queryParamsSubscription =
- this.route.queryParams.subscribe(params => {
- this.overridePlanId = params['planId'];
- });
+ this.route.queryParams.subscribe(params => {
+ this.overridePlanId = params['planId'];
+ }).unsubscribe();
- this.load();
+ this.plansState.load(false, this.overridePlanId).onErrorResumeNext().subscribe();
}
- public load(notifyLoad = false) {
- this.plansService.getPlans(this.appsState.appName)
- .subscribe(dto => {
- if (this.overridePlanId) {
- this.plans = dto.changePlanId(this.overridePlanId);
- } else {
- this.plans = dto;
- }
-
- this.planOwned = !dto.planOwner || (dto.planOwner === this.authState.user!.id);
-
- if (notifyLoad) {
- this.dialogs.notifyInfo('Plans reloaded.');
- }
- }, error => {
- this.dialogs.notifyError(error);
- });
+ public reload() {
+ this.plansState.load(true, this.overridePlanId).onErrorResumeNext().subscribe();
}
- public changePlan(planId: string) {
- this.isDisabled = true;
+ public change(planId: string) {
+ this.plansState.change(planId).onErrorResumeNext().subscribe();
+ }
- this.plansService.putPlan(this.appsState.appName, new ChangePlanDto(planId), this.plans.version)
- .do(() => {
- this.isDisabled = false;
- })
- .subscribe(dto => {
- if (dto.payload.redirectUri && dto.payload.redirectUri.length > 0) {
- window.location.href = dto.payload.redirectUri;
- } else {
- this.plans = this.plans.changePlanId(planId, dto.version);
- }
- }, error => {
- this.dialogs.notifyError(error);
- });
+ public trackByPlan(index: number, plan: PlanDto) {
+ return plan.id;
}
}
diff --git a/src/Squidex/app/shared/internal.ts b/src/Squidex/app/shared/internal.ts
index f1352d6bb..f051c02d6 100644
--- a/src/Squidex/app/shared/internal.ts
+++ b/src/Squidex/app/shared/internal.ts
@@ -44,6 +44,7 @@ export * from './state/backups.state';
export * from './state/clients.state';
export * from './state/contributors.state';
export * from './state/patterns.state';
+export * from './state/plans.state';
export * from './state/rules.state';
export * from './state/schemas.state';
diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts
index 369e610e8..ef72f77ef 100644
--- a/src/Squidex/app/shared/module.ts
+++ b/src/Squidex/app/shared/module.ts
@@ -51,6 +51,7 @@ import {
MustBeNotAuthenticatedGuard,
PatternsState,
PlansService,
+ PlansState,
ResolveAppLanguagesGuard,
ResolveContentGuard,
RichEditorComponent,
@@ -155,6 +156,7 @@ export class SqxSharedModule {
MustBeNotAuthenticatedGuard,
PatternsState,
PlansService,
+ PlansState,
ResolveAppLanguagesGuard,
ResolveContentGuard,
RulesService,
diff --git a/src/Squidex/app/shared/state/clients.state.spec.ts b/src/Squidex/app/shared/state/clients.state.spec.ts
index c966577e7..9c4dd0d12 100644
--- a/src/Squidex/app/shared/state/clients.state.spec.ts
+++ b/src/Squidex/app/shared/state/clients.state.spec.ts
@@ -54,8 +54,7 @@ describe('ClientsState', () => {
});
it('should load clients', () => {
- expect(clientsState.snapshot.clients.values).toEqual(oldClients);
- expect(clientsState.snapshot.isLoaded).toBeTruthy();
+ expect(clientsState.snapshot.clients!.values).toEqual(oldClients);
expect(clientsState.snapshot.version).toEqual(version);
});
@@ -69,7 +68,7 @@ describe('ClientsState', () => {
clientsState.attach(request).subscribe();
- expect(clientsState.snapshot.clients.values).toEqual([...oldClients, newClient]);
+ expect(clientsState.snapshot.clients!.values).toEqual([...oldClients, newClient]);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
@@ -81,7 +80,7 @@ describe('ClientsState', () => {
clientsState.update(oldClients[0], request).subscribe();
- const client_1 = clientsState.snapshot.clients.at(0);
+ const client_1 = clientsState.snapshot.clients!.at(0);
expect(client_1.name).toBe('NewName');
expect(client_1.permission).toBe('NewPermission');
@@ -94,7 +93,7 @@ describe('ClientsState', () => {
clientsState.revoke(oldClients[0]).subscribe();
- expect(clientsState.snapshot.clients.values).toEqual([oldClients[1]]);
+ expect(clientsState.snapshot.clients!.values).toEqual([oldClients[1]]);
expect(clientsState.snapshot.version).toEqual(newVersion);
});
});
\ No newline at end of file
diff --git a/src/Squidex/app/shared/state/clients.state.ts b/src/Squidex/app/shared/state/clients.state.ts
index 63e8239b6..c2332c92e 100644
--- a/src/Squidex/app/shared/state/clients.state.ts
+++ b/src/Squidex/app/shared/state/clients.state.ts
@@ -58,11 +58,9 @@ export class AttachClientForm extends Form
{
}
interface Snapshot {
- clients: ImmutableArray;
+ clients?: ImmutableArray;
- isLoaded: boolean;
-
- version: Version;
+ version?: Version;
}
@Injectable()
@@ -70,15 +68,12 @@ export class ClientsState extends State {
public clients =
this.changes.map(x => x.clients);
- public isLoaded =
- this.changes.map(x => x.isLoaded);
-
constructor(
private readonly appClientsService: AppClientsService,
private readonly appsState: AppsState,
private readonly dialogs: DialogService
) {
- super({ clients: ImmutableArray.empty(), version: new Version(''), isLoaded: false });
+ super({});
}
public load(): Observable {
@@ -94,10 +89,10 @@ export class ClientsState extends State {
}
public attach(request: CreateAppClientDto): Observable {
- return this.appClientsService.postClient(this.appName, request, this.snapshot.version)
+ return this.appClientsService.postClient(this.appName, request, this.version)
.do(dto => {
this.next(s => {
- const clients = s.clients.push(dto.payload);
+ const clients = s.clients!.push(dto.payload);
return { ...s, clients, version: dto.version };
});
@@ -106,10 +101,10 @@ export class ClientsState extends State {
}
public revoke(client: AppClientDto): Observable {
- return this.appClientsService.deleteClient(this.appName, client.id, this.snapshot.version)
+ return this.appClientsService.deleteClient(this.appName, client.id, this.version)
.do(dto => {
this.next(s => {
- const clients = s.clients.filter(c => c.id !== client.id);
+ const clients = s.clients!.filter(c => c.id !== client.id);
return { ...s, clients, version: dto.version };
});
@@ -118,10 +113,10 @@ export class ClientsState extends State {
}
public update(client: AppClientDto, request: UpdateAppClientDto): Observable {
- return this.appClientsService.putClient(this.appName, client.id, request, this.snapshot.version)
+ return this.appClientsService.putClient(this.appName, client.id, request, this.version)
.do(dto => {
this.next(s => {
- const clients = s.clients.replaceBy('id', update(client, request));
+ const clients = s.clients!.replaceBy('id', update(client, request));
return { ...s, clients, version: dto.version };
});
@@ -132,6 +127,10 @@ export class ClientsState extends State {
private get appName() {
return this.appsState.appName;
}
+
+ private get version() {
+ return this.snapshot.version!;
+ }
}
const update = (client: AppClientDto, request: UpdateAppClientDto) =>
diff --git a/src/Squidex/app/shared/state/contributors.state.spec.ts b/src/Squidex/app/shared/state/contributors.state.spec.ts
index cfd8ac784..83ef40e4a 100644
--- a/src/Squidex/app/shared/state/contributors.state.spec.ts
+++ b/src/Squidex/app/shared/state/contributors.state.spec.ts
@@ -59,8 +59,7 @@ describe('ContributorsState', () => {
});
it('should load contributors', () => {
- expect(contributorsState.snapshot.contributors.values).toEqual(oldContributors.map(x => c(x)));
- expect(contributorsState.snapshot.isLoaded).toBeTruthy();
+ expect(contributorsState.snapshot.contributors!.values).toEqual(oldContributors.map(x => c(x)));
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(version);
@@ -76,8 +75,7 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe();
- expect(contributorsState.snapshot.contributors.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]);
- expect(contributorsState.snapshot.isLoaded).toBeTruthy();
+ expect(contributorsState.snapshot.contributors!.values).toEqual([...oldContributors.map(x => c(x)), c(newContributor)]);
expect(contributorsState.snapshot.isMaxReached).toBeTruthy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
@@ -93,8 +91,7 @@ describe('ContributorsState', () => {
contributorsState.assign(request).subscribe();
- expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[0]), c(newContributor)]);
- expect(contributorsState.snapshot.isLoaded).toBeTruthy();
+ expect(contributorsState.snapshot.contributors!.values).toEqual([c(oldContributors[0]), c(newContributor)]);
expect(contributorsState.snapshot.isMaxReached).toBeFalsy();
expect(contributorsState.snapshot.maxContributors).toBe(3);
expect(contributorsState.snapshot.version).toEqual(newVersion);
@@ -106,7 +103,7 @@ describe('ContributorsState', () => {
contributorsState.revoke(oldContributors[0]).subscribe();
- expect(contributorsState.snapshot.contributors.values).toEqual([c(oldContributors[1])]);
+ expect(contributorsState.snapshot.contributors!.values).toEqual([c(oldContributors[1])]);
expect(contributorsState.snapshot.version).toEqual(newVersion);
});
diff --git a/src/Squidex/app/shared/state/contributors.state.ts b/src/Squidex/app/shared/state/contributors.state.ts
index ca5d91ab2..9230730aa 100644
--- a/src/Squidex/app/shared/state/contributors.state.ts
+++ b/src/Squidex/app/shared/state/contributors.state.ts
@@ -45,14 +45,13 @@ interface SnapshotContributor {
}
interface Snapshot {
- contributors: ImmutableArray;
+ contributors?: ImmutableArray;
- isLoaded: boolean;
- isMaxReached: boolean;
+ isMaxReached?: boolean;
maxContributors: number;
- version: Version;
+ version?: Version;
}
@Injectable()
@@ -60,9 +59,6 @@ export class ContributorsState extends State {
public contributors =
this.changes.map(x => x.contributors);
- public isLoaded =
- this.changes.map(x => x.isLoaded);
-
public isMaxReached =
this.changes.map(x => x.isMaxReached);
@@ -75,7 +71,7 @@ export class ContributorsState extends State {
private readonly authState: AuthService,
private readonly dialogs: DialogService
) {
- super({ contributors: ImmutableArray.empty(), version: new Version(''), isLoaded: false, isMaxReached: true, maxContributors: -1 });
+ super({ maxContributors: -1 });
}
public load(): Observable {
@@ -89,9 +85,9 @@ export class ContributorsState extends State {
}
public revoke(contributor: AppContributorDto): Observable {
- return this.appContributorsService.deleteContributor(this.appName, contributor.contributorId, this.snapshot.version)
+ return this.appContributorsService.deleteContributor(this.appName, contributor.contributorId, this.version)
.do(dto => {
- const contributors = this.snapshot.contributors.filter(x => x.contributor.contributorId !== contributor.contributorId);
+ const contributors = this.snapshot.contributors!.filter(x => x.contributor.contributorId !== contributor.contributorId);
this.replaceContributors(contributors, dto.version);
})
@@ -99,11 +95,11 @@ export class ContributorsState extends State {
}
public assign(request: AppContributorDto): Observable {
- return this.appContributorsService.postContributor(this.appName, request, this.snapshot.version)
+ return this.appContributorsService.postContributor(this.appName, request, this.version)
.do(dto => {
const contributor = this.createContributor(new AppContributorDto(dto.payload.contributorId, request.permission));
- let contributors = this.snapshot.contributors;
+ let contributors = this.snapshot.contributors!;
if (contributors.find(x => x.contributor.contributorId === dto.payload.contributorId)) {
contributors = contributors.map(c => c.contributor.contributorId === dto.payload.contributorId ? contributor : c);
@@ -131,6 +127,10 @@ export class ContributorsState extends State {
return this.appsState.appName;
}
+ private get version() {
+ return this.snapshot.version!;
+ }
+
private createContributor(contributor: AppContributorDto): SnapshotContributor {
return { contributor, isCurrentUser: contributor.contributorId === this.authState.user!.id };
}
diff --git a/src/Squidex/app/shared/state/patterns.state.ts b/src/Squidex/app/shared/state/patterns.state.ts
index 5ff47fe2a..906e527f2 100644
--- a/src/Squidex/app/shared/state/patterns.state.ts
+++ b/src/Squidex/app/shared/state/patterns.state.ts
@@ -89,7 +89,7 @@ export class PatternsState extends State {
}
public create(request: EditAppPatternDto): Observable {
- return this.appPatternsService.postPattern(this.appName, request, this.snapshot.version)
+ return this.appPatternsService.postPattern(this.appName, request, this.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.push(dto.payload).sortByStringAsc(x => x.name);
@@ -101,7 +101,7 @@ export class PatternsState extends State {
}
public update(pattern: AppPatternDto, request: EditAppPatternDto): Observable {
- return this.appPatternsService.putPattern(this.appName, pattern.id, request, this.snapshot.version)
+ return this.appPatternsService.putPattern(this.appName, pattern.id, request, this.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.replaceBy('id', update(pattern, request)).sortByStringAsc(x => x.name);
@@ -113,7 +113,7 @@ export class PatternsState extends State {
}
public delete(pattern: AppPatternDto): Observable {
- return this.appPatternsService.deletePattern(this.appName, pattern.id, this.snapshot.version)
+ return this.appPatternsService.deletePattern(this.appName, pattern.id, this.version)
.do(dto => {
this.next(s => {
const patterns = s.patterns.filter(c => c.id !== pattern.id);
@@ -127,6 +127,10 @@ export class PatternsState extends State {
private get appName() {
return this.appsState.appName;
}
+
+ private get version() {
+ return this.snapshot.version;
+ }
}
const update = (pattern: AppPatternDto, request: EditAppPatternDto) =>
diff --git a/src/Squidex/app/shared/state/plans.state.ts b/src/Squidex/app/shared/state/plans.state.ts
new file mode 100644
index 000000000..c694008ee
--- /dev/null
+++ b/src/Squidex/app/shared/state/plans.state.ts
@@ -0,0 +1,131 @@
+/*
+ * Squidex Headless CMS
+ *
+ * @license
+ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
+ */
+
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+
+import '@app/framework/utils/rxjs-extensions';
+
+import {
+ DialogService,
+ ImmutableArray,
+ State,
+ Version
+} from '@app/framework';
+
+import { AppsState } from './apps.state';
+import { AuthService } from './../services/auth.service';
+
+import {
+ ChangePlanDto,
+ PlanDto,
+ PlansService
+} from './../services/plans.service';
+
+interface PlanInfo {
+ plan: PlanDto;
+
+ isYearlySelected?: boolean;
+ isSelected?: boolean;
+}
+
+interface Snapshot {
+ plans?: ImmutableArray;
+
+ version?: Version;
+
+ isOwner?: boolean;
+ isDisabled?: boolean;
+
+ hasPortal?: boolean;
+}
+
+@Injectable()
+export class PlansState extends State {
+ public plans =
+ this.changes.map(x => x.plans);
+
+ public isOwner =
+ this.changes.map(x => x.isOwner);
+
+ public isDisabled =
+ this.changes.map(x => x.isDisabled || !x.isOwner);
+
+ public hasPortal =
+ this.changes.map(x => x.hasPortal);
+
+ public window = window;
+
+ constructor(
+ private readonly appsState: AppsState,
+ private readonly authState: AuthService,
+ private readonly dialogs: DialogService,
+ private readonly plansService: PlansService
+ ) {
+ super({});
+ }
+
+ public load(notifyLoad = false, overridePlanId?: string): Observable {
+ return this.plansService.getPlans(this.appName)
+ .do(dto => {
+ if (notifyLoad) {
+ this.dialogs.notifyInfo('Plans reloaded.');
+ }
+
+ this.next(s => {
+ const planId = overridePlanId || dto.currentPlanId;
+
+ return {
+ ...s,
+ plans: ImmutableArray.of(dto.plans.map(x => this.createPlan(x, planId))),
+ isOwner: !dto.planOwner || dto.planOwner === this.userId,
+ isDisabled: false,
+ hasPortal: dto.hasPortal
+ };
+ });
+ })
+ .notify(this.dialogs);
+ }
+
+ public change(planId: string): Observable {
+ this.next(s => ({ ...s, isDisabled: true }));
+
+ return this.plansService.putPlan(this.appName, new ChangePlanDto(planId), this.version)
+ .do(dto => {
+ if (dto.payload.redirectUri && dto.payload.redirectUri.length > 0) {
+ this.window.location.href = dto.payload.redirectUri;
+ } else {
+ this.next(s => {
+ return {
+ ...s,
+ plans: s.plans!.map(x => this.createPlan(x.plan, planId)),
+ isOwner: true,
+ isDisabled: false
+ };
+ });
+ }
+ })
+ .notify(this.dialogs);
+ }
+
+ private createPlan(plan: PlanDto, id: string) {
+ return { plan, isYearlySelected: plan.yearlyId === id, isSelected: plan.id === id };
+ }
+
+ private get appName() {
+ return this.appsState.appName;
+ }
+
+ private get userId() {
+ return this.authState.user!.id;
+ }
+
+ private get version() {
+ return this.snapshot.version!;
+ }
+}
+