Skip to content

Commit dccd8df

Browse files
committed
implemented subscription form
1 parent 3d4cd72 commit dccd8df

File tree

9 files changed

+319
-11
lines changed

9 files changed

+319
-11
lines changed

src/apim.runtime.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ApiProductsCards } from "./components/template-pages/api-details-page/k
3131
import { ApiNavMenu } from "./components/template-pages/api-details-page/ko/runtime/menu/api-nav-menu";
3232
import {ProductNavMenu} from "./components/template-pages/product-details-page/ko/runtime/menu/product-nav-menu";
3333
import { ProductApisCards } from "./components/template-pages/product-details-page/ko/runtime/staticPages/product-apis-cards";
34+
import { SubscriptionForm } from "./components/template-pages/product-details-page/ko/runtime/subscripion-form/subscription-form";
3435
import { Changelog } from "./components/template-pages/api-details-page/ko/runtime/staticPages/changelog";
3536
import { WikiDocumentation } from "./components/template-pages/common/ko/runtime/wikiDocumentation/wiki-documentation";
3637
import { ProdutDetailsPageSubscriptions } from "./components/template-pages/product-details-page/ko/runtime/staticPages/product-details-page-subscriptions";
@@ -120,6 +121,7 @@ export class ApimRuntimeModule implements IInjectorModule {
120121
injector.bind("apiNavmenu", ApiNavMenu);
121122
injector.bind("productNavmenu", ProductNavMenu);
122123
injector.bind("productApisCards", ProductApisCards);
124+
injector.bind("subsctiptionForm", SubscriptionForm);
123125
injector.bind("changelog", Changelog);
124126
injector.bind("wikiDocumentation", WikiDocumentation);
125127
injector.bind("produtDetailsPageSubscriptions", ProdutDetailsPageSubscriptions);

src/components/template-pages/product-details-page/ko/runtime/product-details-page.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
1+
<div id="widgetPopupContainer" class="widget-popup-container" data-bind="css: { 'show': showSubscribeForm() }">
2+
<div class="popup-content">
3+
<subscription-form
4+
params="{closeSubscriptionForm: closeSubscriptionForm, product: product}"></subscription-form>
5+
</div>
6+
</div>
7+
8+
</div>
19
<div class="page-container">
210
<div class="page-title title-background">
311
<h1 class="title-text"
412
data-bind="text: product() ? product().displayName : 'The specified product does not exsist'"></h1>
5-
<button class="subscribe-button" data-bind="click: openSubscribeForm">
13+
<button class="subscribe-button" data-bind="enable: canCreateNewSubscription ,click: openSubscriptionForm">
614
<span>Subscribe</span>
715
<i class="icon-emb icon-emb-plus"></i>
816
</button>
@@ -50,7 +58,7 @@ <h3>About this Product</h3>
5058

5159
<!-- ko if: selectedMenuItem()?.value === 'subscriptions' -->
5260
<product-details-page-subscriptions
53-
params="{ product: product, openSubscribeForm: openSubscribeForm}"></product-details-page-subscriptions>
61+
params="{ product: product, openSubscriptionForm: openSubscriptionForm, canCreateNewSubscription: canCreateNewSubscription, calculateCanCreateNewSubscription: calculateCanCreateNewSubscription }"></product-details-page-subscriptions>
5462
<!-- /ko -->
5563
<!-- /ko -->
5664

src/components/template-pages/product-details-page/ko/runtime/product-details-page.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { breadcrumbItem, menuItem, menuItemType } from "../../../common/Utils";
66
import { ProductService } from "../../../../../services/productService";
77
import { RouteHelper } from "../../../../../routing/routeHelper";
88
import { Router } from "@paperbits/common/routing";
9-
9+
import { SubscriptionState } from "../../../../../contracts/subscription";
10+
import { UsersService } from "../../../../../services/usersService";
1011

1112
@RuntimeComponent({
1213
selector: "product-details-page"
@@ -20,20 +21,26 @@ export class ProductDetailsPage {
2021
public readonly breadcrumbItems: ko.Observable<breadcrumbItem[]>;
2122
public readonly productLoading: ko.Observable<boolean>;
2223
public readonly selectedMenuItem: ko.Observable<menuItem>;
24+
public readonly showSubscribeForm: ko.Observable<boolean>;
25+
public readonly canCreateNewSubscription: ko.Observable<boolean>;
2326

2427
@Param()
2528
public wrapText: ko.Observable<boolean>;
2629

2730
constructor(
2831
private readonly productService: ProductService,
2932
private readonly routeHelper: RouteHelper,
30-
private readonly router: Router
33+
private readonly router: Router,
34+
private readonly usersService: UsersService
3135
) {
3236
this.product = ko.observable();
3337
this.breadcrumbItems = ko.observable([]);
3438
this.productLoading = ko.observable(true);
3539
this.selectedMenuItem = ko.observable();
3640
this.wrapText = ko.observable();
41+
this.showSubscribeForm = ko.observable(false);
42+
this.canCreateNewSubscription = ko.observable(false);
43+
this.calculateCanCreateNewSubscription = this.calculateCanCreateNewSubscription.bind(this);
3744
}
3845

3946
@OnMounted()
@@ -54,17 +61,39 @@ export class ProductDetailsPage {
5461
url = this.routeHelper.getProductDocumentationReferenceUrl(this.product().name, menuItem.value);
5562
}
5663

57-
console.log(url);
58-
5964
const breadcrumbs = this.breadcrumbItems();
6065
breadcrumbs.pop();
6166
breadcrumbs.push({ title: menuItem.displayName, url: url });
6267
this.breadcrumbItems(breadcrumbs);
6368
});
6469
}
6570

66-
public openSubscribeForm(){
67-
alert("Subscribe form opened");
71+
public openSubscriptionForm() {
72+
this.showSubscribeForm(true);
73+
}
74+
75+
public closeSubscriptionForm(reloadSubscriptions?: boolean) {
76+
this.showSubscribeForm(false);
77+
78+
if (!!reloadSubscriptions) {
79+
this.loadProduct(this.product().name);
80+
}
81+
}
82+
83+
public async calculateCanCreateNewSubscription(): Promise<void> {
84+
const userId = await this.usersService.getCurrentUserId();
85+
86+
if (!userId) {
87+
this.canCreateNewSubscription(false);
88+
return;
89+
}
90+
91+
const subscriptions = await this.productService.getSubscriptionsForProduct(userId, this.product().id);
92+
const activeSubscriptions = subscriptions.value.filter(item => item.state === SubscriptionState.active || item.state === SubscriptionState.submitted) || [];
93+
const numberOfSubscriptions = activeSubscriptions.length;
94+
const limitReached = (this.product().subscriptionsLimit || this.product().subscriptionsLimit === 0) && this.product().subscriptionsLimit <= numberOfSubscriptions;
95+
96+
this.canCreateNewSubscription(!limitReached);
6897
}
6998

7099
private async loadProduct(productName: string): Promise<void> {
@@ -74,6 +103,7 @@ export class ProductDetailsPage {
74103
this.product(product);
75104

76105
this.initializeBreadcrumbItems();
106+
await this.calculateCanCreateNewSubscription();
77107
}
78108
catch (error) {
79109
console.error(`Unable to load product: ${error}`);

src/components/template-pages/product-details-page/ko/runtime/staticPages/product-details-page-subscriptions.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,16 +96,19 @@ <h3>Subscriptions</h3>
9696
<!-- /ko -->
9797
<!-- /ko -->
9898

99+
<!-- ko if: canCreateNewSubscription()-->
99100
<div class="table-row">
100101
<div class="col-auto">
101102
<div class="text-truncate">
102-
<a href="#" data-bind="click: openSubscribeForm">
103+
<a href="#" data-bind="click: openSubscriptionForm">
103104
<i class="icon-emb icon-emb-plus"></i>
104105
<span>Subscribe</span>
105106
</a>
106107
</div>
107108
</div>
108109
</div>
110+
<!-- /ko -->
111+
109112
</div>
110113
<!-- /ko -->
111114
</div>

src/components/template-pages/product-details-page/ko/runtime/staticPages/product-details-page-subscriptions.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,17 @@ export class ProdutDetailsPageSubscriptions {
2727
public readonly nextLink: ko.Observable<string>;
2828
public readonly loadModeSubscriptions: ko.Observable<boolean>;
2929

30+
@Param()
31+
public readonly canCreateNewSubscription: ko.Observable<boolean>;
32+
3033
@Param()
3134
public readonly product: ko.Observable<Product>;
3235

3336
@Param()
34-
public readonly openSubscribeForm: () => void;
37+
public readonly openSubscriptionForm: () => void;
38+
39+
@Param()
40+
public readonly calculateCanCreateNewSubscription: () => Promise<void>;
3541

3642
constructor(
3743
private readonly usersService: UsersService,
@@ -48,6 +54,7 @@ export class ProdutDetailsPageSubscriptions {
4854
this.nextLink = ko.observable();
4955
this.product = ko.observable();
5056
this.loadModeSubscriptions = ko.observable(false);
57+
this.canCreateNewSubscription = ko.observable();
5158
}
5259

5360
@OnMounted()
@@ -99,6 +106,7 @@ export class ProdutDetailsPageSubscriptions {
99106
const updatedVM = new SubscriptionListItem(updated, this.eventManager);
100107
this.syncSubscriptionLabelState(subscription, updatedVM);
101108
this.subscriptions.replace(subscription, updatedVM);
109+
await this.calculateCanCreateNewSubscription();
102110
}
103111
catch (error) {
104112
if (error.code === "Unauthorized") {
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<div class="panel subscription-form">
2+
<div class="panel-header">
3+
<h3>Create subscription</h3>
4+
<button type="button" tabindex="0" class="no-border pull-right" data-dismiss="modal"
5+
aria-label="Close subscription form" data-bind="click: closeSubscriptionForm(false)">
6+
<i class="icon-emb icon-emb-simple-remove"></i>
7+
</button>
8+
</div>
9+
<div>
10+
<!-- ko ifnot: isDelegationEnabled() -->
11+
<div class="form-group">
12+
<label for="subscriptionName">Subscription name *</label>
13+
<input id="subscriptionName" aria-required="true" type="text" class="form-control"
14+
data-bind="textInput: subscriptionName" />
15+
</div>
16+
<!-- /ko -->
17+
18+
<!-- ko if: product()?.terms -->
19+
<div class="form-group">
20+
<label for="termsOfUse">Terms of use</label>
21+
<div id="termsOfUse" class="terms-of-use" data-bind="html: product()?.terms"></div>
22+
</div>
23+
<input id="consentCheckbox" type="checkbox" class="form-check-input" data-bind="checked: consented" />
24+
<label for="consentCheckbox" class="form-check-label"> I agree to the Terms of Use.</label>
25+
<!-- /ko -->
26+
27+
<!-- ko if: working() -->
28+
<spinner></spinner>
29+
<!-- /ko -->
30+
31+
<!-- ko ifnot: working() -->
32+
<div class="panel-footer">
33+
<button type="button" class="button button-primary create-button"
34+
data-bind="click: subscribe, enable: canSubscribe">Create</button>
35+
<button type="button discard-button" class="button"
36+
data-bind="click: closeSubscriptionForm">Discard</button>
37+
</div>
38+
<!-- /ko -->
39+
40+
<!-- ko if: hasError() -->
41+
<div class="alert alert-danger" data-bind="scrollintoview: {}">
42+
<p>Unable to create subscription</p>
43+
<p>Something went wrong. Please try again later.</p>
44+
</div>
45+
<!-- /ko -->
46+
</div>
47+
</div>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import * as ko from "knockout";
2+
import template from "./subscription-form.html";
3+
import { Component, Param, OnMounted } from "@paperbits/common/ko/decorators";
4+
import { Product } from "../../../../../../models/product";
5+
import { UsersService } from "../../../../../../services";
6+
import { Utils } from "../../../../../../utils";
7+
import { ProductService } from "../../../../../../services/productService";
8+
import { TenantService } from "../../../../../../services/tenantService";
9+
import { DelegationAction, DelegationParameters } from "../../../../../../contracts/tenantSettings";
10+
import { BackendService } from "../../../../../../services/backendService";
11+
import { RouteHelper } from "../../../../../../routing/routeHelper";
12+
13+
@Component({
14+
selector: "subscription-form",
15+
template: template
16+
})
17+
export class SubscriptionForm {
18+
public readonly subscriptionName: ko.Observable<string>;
19+
public readonly consented: ko.Observable<boolean>;
20+
public readonly canSubscribe: ko.Computed<boolean>;
21+
public readonly working: ko.Observable<boolean>;
22+
public readonly hasError: ko.Observable<boolean>;
23+
public readonly isDelegationEnabled: ko.Observable<boolean>;
24+
product: ko.Observable<Product>;
25+
26+
@Param()
27+
closeSubscriptionForm: (reloadSubscriptions?: boolean) => void;
28+
29+
constructor(private readonly usersService: UsersService,
30+
private readonly productService: ProductService,
31+
private readonly tenantService: TenantService,
32+
private readonly backendService: BackendService,
33+
private readonly routeHelper: RouteHelper
34+
) {
35+
this.subscriptionName = ko.observable("");
36+
this.consented = ko.observable(false);
37+
this.working = ko.observable(false);
38+
this.hasError = ko.observable(false);
39+
this.isDelegationEnabled = ko.observable(false);
40+
41+
this.product = ko.observable();
42+
43+
this.canSubscribe = ko.pureComputed((): boolean => {
44+
return (this.isDelegationEnabled() || this.subscriptionName().length > 0) && ((this.product().terms && this.consented()) || !this.product().terms);
45+
});
46+
}
47+
48+
@OnMounted()
49+
public async initialize(): Promise<void> {
50+
const userId = await this.usersService.getCurrentUserId();
51+
52+
if (!userId) {
53+
return;
54+
}
55+
56+
const productName = this.routeHelper.getProductName();
57+
58+
if (!productName) {
59+
return;
60+
}
61+
62+
this.product(await this.productService.getProduct(`products/${productName}`));
63+
this.isDelegationEnabled(await this.tenantService.isSubscriptionDelegationEnabled());
64+
}
65+
66+
public async subscribe(): Promise<void> {
67+
if (!this.canSubscribe()) {
68+
return;
69+
}
70+
71+
const userId = await this.usersService.ensureSignedIn();
72+
73+
if (!userId) {
74+
return;
75+
}
76+
77+
this.working(true);
78+
this.hasError(false);
79+
80+
try {
81+
if (this.isDelegationEnabled()) {
82+
const delegationParam = {};
83+
delegationParam[DelegationParameters.ProductId] = this.product().name;
84+
delegationParam[DelegationParameters.UserId] = Utils.getResourceName("users", userId);
85+
const delegationUrl = await this.backendService.getDelegationString(DelegationAction.subscribe, delegationParam);
86+
if (delegationUrl) {
87+
location.assign(delegationUrl);
88+
return;
89+
}
90+
}
91+
92+
const subscriptionId = `/subscriptions/${Utils.getBsonObjectId()}`;
93+
await this.productService.createSubscription(subscriptionId, userId, `/products/${this.product().name}`, this.subscriptionName());
94+
95+
96+
this.closeSubscriptionForm(true);
97+
}
98+
catch (error) {
99+
this.hasError(true);
100+
}
101+
finally {
102+
this.working(false);
103+
}
104+
105+
}
106+
}

0 commit comments

Comments
 (0)