UNPKG

@vendasta/store

Version:

Components and data for Store

780 lines (764 loc) 380 kB
import { Component, Input, NgModule, EventEmitter, Output, Injectable, ChangeDetectorRef } from '@angular/core'; import { formatDisplayPrice, formatBillingFrequency, Addon } from '@vendasta/core/shared'; import { CommonModule } from '@angular/common'; import { ImageTransformationService, ImageTransformationModule } from '@vendasta/core/image-transformation'; import { MatButtonModule, MatCardModule, MatIconModule, MatMenuModule, MatChipsModule, MatTooltipModule, MatCheckboxModule, MatExpansionModule, MatInputModule, MatOptionModule, MatProgressSpinnerModule, MatSelectModule, MatDividerModule, MatListModule } from '@angular/material'; import { VaIconModule, VaSafeHtmlModule, VaBreadcrumbsModule, VaImageGalleryModule, VaListModule, UIKitModule } from '@vendasta/uikit'; import { FormControl, FormGroup, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { BillingModel, BillingService, buildBilledProductFromProductPricingAndAddon, BillingModule } from '@vendasta/core/billing'; import { VaFormsModule } from '@vendasta/forms'; import { MatProgressSpinnerModule as MatProgressSpinnerModule$1 } from '@angular/material/progress-spinner'; import { isNullOrUndefined } from 'util'; import { BehaviorSubject, ReplaySubject, combineLatest, merge, zip } from 'rxjs'; import { skipWhile, shareReplay, map, take, filter, startWith } from 'rxjs/operators'; import { trigger, transition, animate, style, state } from '@angular/animations'; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class VaPricingComponent { constructor() { this.pricing = null; this.wrapFrequency = false; this.isAddon = false; this.hasVerifiedContract = false; this.highlightPrice = true; this.loaded = true; } /** * @return {?} */ get isFree() { /** @type {?} */ const isFreeHelper = (prices) => { return prices ? prices.some(p => p.price === 0 || p.price === undefined) : false; }; if (this.hasVerifiedContract && this.billedProduct) { return this.billedProduct.pricingTiers.length === 1 ? isFreeHelper(this.billedProduct.pricingTiers) : false; } return this.pricing && isFreeHelper(this.pricing.prices) ? true : false; } /** * @return {?} */ get shouldContactSales() { /** @type {?} */ const nestedPricesIsContactSales = (prices) => { return prices && prices.length > 0 ? prices.some(p => p.price === null || p.price < 0) : true; }; if (this.hasVerifiedContract && this.billedProduct) { return nestedPricesIsContactSales(this.billedProduct.pricingTiers) ? true : false; } if (!this.pricing) { return true; } return nestedPricesIsContactSales(this.pricing.prices); } /** * @param {?} billedProduct * @return {?} */ isFlatPrice(billedProduct) { return billedProduct.pricingTiers.length === 1; } /** * @param {?} tier * @return {?} */ buildPricingTierString(tier) { /** @type {?} */ let max = String(tier.rangeMax); if (tier.rangeMax === -1) { max = '∞'; if (tier.rangeMin === 0 || tier.rangeMin === 1) { return ''; } } return String(tier.rangeMin) + ' to ' + max; } /** * @param {?} tier * @param {?=} frequency * @return {?} */ buildPricingTierForProduct(tier, frequency) { /** @type {?} */ const pricingTier = this.buildPricingTierString(tier); /** @type {?} */ const formattedFrequency = formatBillingFrequency(frequency); return pricingTier ? pricingTier + ' accounts ' + formattedFrequency : formattedFrequency; } /** * @param {?} tier * @return {?} */ buildPricingTierForAddon(tier) { /** @type {?} */ const pricingRange = this.buildPricingTierString(tier); return pricingRange ? pricingRange : ''; } /** * @param {?} price * @param {?} currency * @param {?=} excludeFrequency * @return {?} */ buildPriceStringForPricing(price, currency, excludeFrequency) { return formatDisplayPrice(price.price, currency, (/** @type {?} */ ((excludeFrequency ? '' : price.frequency))), true, true, true, price.isStartingPrice); } /** * @param {?} tier * @param {?=} frequency * @param {?=} isStartingPrice * @return {?} */ buildPriceStringForTier(tier, frequency, isStartingPrice) { return formatDisplayPrice(tier.price, this.billedProduct.currency, (/** @type {?} */ (frequency)), undefined, undefined, undefined, isStartingPrice); } /** * @param {?=} frequency * @return {?} */ buildFrequencyString(frequency) { return formatBillingFrequency(frequency); } /** * @return {?} */ buildCommitmentMessage() { /** @type {?} */ const frequency = this.billedProduct ? this.billedProduct.billingFrequency.toString().toLowerCase() : this.pricing && this.pricing.prices.length > 0 ? this.pricing.prices[0].frequency.toLowerCase() : ''; /** @type {?} */ let frequencyString = ''; if (frequency === 'yearly') { frequencyString = 'year'; } else if (frequency === 'monthly') { frequencyString = 'month'; } if (!frequencyString || this.isFree || this.shouldContactSales) { return ''; } if (this.billedProduct && this.billedProduct.commitment) { /** @type {?} */ const initial = this.billedProduct.commitment.initial; /** @type {?} */ const recurring = this.billedProduct.commitment.recurring; if (this.highlightPrice) { return `*${initial} ${frequencyString} minimum, renews for ${recurring} ${frequencyString} periods`; } else { return `${initial} ${frequencyString} commitment`; } } return ''; } } VaPricingComponent.decorators = [ { type: Component, args: [{ selector: 'va-pricing', template: "<ng-container *ngIf=\"highlightPrice\">\n <highlight-pricing [pricing]=\"pricing\"\n [billedProduct]=\"billedProduct\"\n [highlightPrice]=\"highlightPrice\"\n [isAddon]=\"isAddon\"\n [hasVerifiedContract]=\"hasVerifiedContract\"\n [wrapFrequency]=\"wrapFrequency\"\n [loaded]=\"loaded\">\n </highlight-pricing>\n</ng-container>\n<ng-container *ngIf=\"!highlightPrice\">\n <table-pricing [pricing]=\"pricing\"\n [highlightPrice]=\"highlightPrice\"\n [billedProduct]=\"billedProduct\"\n [hasVerifiedContract]=\"hasVerifiedContract\"\n [loaded]=\"loaded\">\n </table-pricing>\n</ng-container>\n", styles: [""] }] } ]; VaPricingComponent.propDecorators = { pricing: [{ type: Input }], billedProduct: [{ type: Input }], wrapFrequency: [{ type: Input }], isAddon: [{ type: Input }], hasVerifiedContract: [{ type: Input }], highlightPrice: [{ type: Input }], loaded: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class HighlightPricingComponent extends VaPricingComponent { } HighlightPricingComponent.decorators = [ { type: Component, args: [{ selector: 'highlight-pricing', template: "<ng-container *ngIf=\"loaded; else loading\">\n <div class=\"no-price\" *ngIf=\"shouldContactSales; else dontContactSales\">\n Contact Sales\n </div>\n <ng-template #dontContactSales>\n <div class=\"no-price\" *ngIf=\"isFree; else notFree\">\n <b>Free</b>\n </div>\n <ng-template #notFree>\n <ng-container *ngIf=\"!isAddon\">\n <ng-container *ngIf=\"hasVerifiedContract && billedProduct; else usePricing\">\n <div *ngFor=\"let tier of billedProduct.pricingTiers\" class=\"price-box\">\n <span>\n <span *ngIf=\"tier.isStartingPrice\" class=\"starting-at\">Starting at</span>\n <b class=\"price-number\">{{buildPriceStringForTier(tier)}}</b>\n </span>\n <span class=\"tier-billing-freq\" [ngClass]=\"{'va-wrap': wrapFrequency}\">\n {{buildPricingTierForProduct(tier, billedProduct.billingFrequency)}}\n </span>\n </div>\n </ng-container>\n <ng-template #usePricing>\n <ng-container *ngIf=\"pricing?.prices\">\n <div *ngFor=\"let price of pricing.prices; let i = index\" class=\"price-box\">\n <span>\n <span *ngIf=\"price?.isStartingPrice\" class=\"starting-at\">Starting at</span>\n <b class=\"price-number\">\n <ng-container *ngIf=\"i == 1\">+</ng-container>\n {{buildPriceStringForPricing(price, pricing?.currency, true)}}\n </b>\n </span>\n <span *ngIf=\"price.frequency as frequency\" class=\"billing-freq\" [ngClass]=\"{'va-wrap': wrapFrequency}\">\n {{buildFrequencyString(frequency)}}\n </span>\n </div>\n </ng-container>\n </ng-template>\n </ng-container>\n <ng-container *ngIf=\"isAddon\">\n <div *ngIf=\"hasVerifiedContract && billedProduct; else useAddonPricing\">\n <div *ngFor=\"let tier of billedProduct.pricingTiers\" class=\"price-box\">\n <span class=\"addon-billing-freq\">\n {{buildPricingTierForAddon(tier)}}\n </span>\n <span>\n <span *ngIf=\"tier.isStartingPrice\" class=\"starting-at\">Starting at</span>\n <b class=\"price-number\">\n {{buildPriceStringForTier(tier, billedProduct.billingFrequency)}}\n </b>\n </span>\n\n </div>\n </div>\n <ng-template #useAddonPricing>\n <div class=\"price-box\">\n <span *ngIf=\"pricing?.prices[0].frequency\" class=\"addon-billing-freq\">\n {{buildFrequencyString(pricing.prices[0].frequency)}}\n </span>\n <span>\n <span *ngIf=\"pricing?.prices[0]?.isStartingPrice\" class=\"starting-at\">Starting at</span>\n <b class=\"price-number\">\n {{buildPriceStringForPricing(pricing?.prices[0], pricing?.currency, true)}}\n </b>\n </span>\n </div>\n </ng-template>\n </ng-container>\n <div class=\"commitment\" *ngIf=\"billedProduct && billedProduct.commitment && billedProduct.commitment.initial > 1\">\n {{ buildCommitmentMessage() }}\n </div>\n </ng-template>\n </ng-template>\n</ng-container>\n<ng-template #loading>\n <div class=\"stencil-pricing stencil-shimmer\"></div>\n</ng-template>\n", styles: [":host{color:#9e9e9e}.starting-at{margin-right:5px}.price-box{display:flex;align-items:baseline;flex-direction:row-reverse}.no-price{text-align:center}b{color:#212121;font-size:20px}@media screen and (max-width:600px){.no-price,.price-box,b{font-size:16px}}.billing-freq{text-transform:capitalize;flex:1}.tier-billing-freq{flex:1}.addon-billing-freq{padding-left:5px}.va-wrap{display:block}.stencil-pricing{height:1em}.commitment{color:#9e9e9e;font-size:12px;font-style:italic}"] }] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class TablePricingComponent extends VaPricingComponent { } TablePricingComponent.decorators = [ { type: Component, args: [{ selector: 'table-pricing', template: "<ng-container *ngIf=\"loaded; else loading\">\n <div *ngIf=\"hasVerifiedContract && billedProduct; else usePricing\">\n <div class=\"price-container\" *ngIf=\"isFlatPrice(billedProduct); else notFlatPrice\">\n <div class=\"price\">\n {{buildPriceStringForTier(billedProduct.pricingTiers[0], billedProduct.billingFrequency, billedProduct.pricingTiers[0].isStartingPrice)}}\n </div>\n </div>\n <ng-template #notFlatPrice>\n <div class=\"price-container--tiered\" *ngIf=\"!isFlatPrice(billedProduct)\">\n <div class=\"pricing-tier\" *ngFor=\"let tier of billedProduct.pricingTiers\">\n <div class=\"pricing-tier__range\">{{buildPricingTierString(tier)}}</div>\n <div class=\"pricing-tier__price\">\n {{buildPriceStringForTier(tier, billedProduct.billingFrequency, tier.isStartingPrice)}}\n </div>\n </div>\n </div>\n </ng-template>\n <div class=\"commitment\" *ngIf=\"billedProduct && billedProduct.commitment && billedProduct.commitment.initial > 1\">\n {{ buildCommitmentMessage() }}\n </div>\n </div>\n <ng-template #usePricing>\n <div class=\"gray-font\">\n <ng-container *ngIf=\"shouldContactSales; else dontContactSales\">\n Contact Sales\n </ng-container>\n <ng-template #dontContactSales>\n <ng-container *ngIf=\"isFree; else notFree\">\n Free\n </ng-container>\n <ng-template #notFree>\n {{buildPriceStringForPricing(pricing?.prices[0], pricing?.currency)}}\n <div class=\"commitment\" *ngIf=\"billedProduct && billedProduct.commitment && billedProduct.commitment.initial > 1\">\n {{ buildCommitmentMessage() }}\n </div>\n </ng-template>\n </ng-template>\n </div>\n </ng-template>\n</ng-container>\n<ng-template #loading>\n <div class=\"stencil-pricing stencil-shimmer\"></div>\n</ng-template>\n", styles: [":host{color:#616161}.price-container--tiered{font-size:12px;flex-direction:column;display:flex;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content}.price-container--tiered .pricing-tier{border-bottom:1px solid #e0e0e0;padding:5px 0;display:flex}.price-container--tiered .pricing-tier:first-child{padding-top:0}.price-container--tiered .pricing-tier:last-child{padding-bottom:0;border-bottom:none}.price-container--tiered .pricing-tier .pricing-tier__range{padding-right:20px;flex:1}.stencil-pricing{height:1em}.commitment{color:#9e9e9e;font-size:12px;font-style:italic}"] }] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class VaPricingModule { } VaPricingModule.decorators = [ { type: NgModule, args: [{ imports: [CommonModule], declarations: [VaPricingComponent, HighlightPricingComponent, TablePricingComponent], exports: [VaPricingComponent] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class StoreCardComponent { /** * @param {?} imageTransformationService */ constructor(imageTransformationService) { this.imageTransformationService = imageTransformationService; this.purchasedClicked = new EventEmitter(); this.cardClicked = new EventEmitter(); this.descriptionClicked = new EventEmitter(); this.bannerClicked = new EventEmitter(); } /** * @return {?} */ getBannerColorForName() { // determine an icon color for a product with no icon by using the product name /** @type {?} */ const COLOR_CODES = [ '#EF5350', '#42A5F5', '#66BB6A', '#FFA726', '#AB47BC', '#FFCA28', '#EC407A', '#26C6DA', '#FF7B57' ]; /** @type {?} */ let nameSum = 0; /** @type {?} */ const defaultColor = '#808080'; if (!this.item.name) { return defaultColor; } for (let i = 0; i < this.item.name.length; i++) { nameSum += this.item.name[i].charCodeAt(0); } /** @type {?} */ const index = nameSum % COLOR_CODES.length; return COLOR_CODES[index]; } /** * @param {?} imageUrl * @return {?} */ getSrcsetHeaderUrls(imageUrl) { return this.imageTransformationService.getSrcSetForImage(imageUrl, [680, 1360, 2720]); } } StoreCardComponent.decorators = [ { type: Component, args: [{ selector: 'va-store-card', template: ` <mat-card class="hover-card" *ngIf="item" (click)="cardClicked.emit(item)"> <div class="hover-area"> <div class="product-banner" (click)="bannerClicked.emit(item)" [style.background-color]="item.headerImageUrl ? '': getBannerColorForName()"> <img class="img-container" *ngIf="item.headerImageUrl" [src]="item.headerImageUrl" [srcset]="getSrcsetHeaderUrls(item.headerImageUrl)"> </div> <div class="description" (click)="descriptionClicked.emit(item)"> <mat-card-header> <va-icon mat-card-avatar [diameter]="40" [name]="item.name" [iconUrl]="item.iconUrl"></va-icon> <mat-card-title>{{item.name}}</mat-card-title> <mat-card-subtitle> <ng-container *ngIf="item.pricing; else formatted"> <va-pricing [pricing]="item.pricing" [highlightPrice]="false"></va-pricing> </ng-container> <ng-template #formatted>{{item.formattedPrice}}</ng-template> </mat-card-subtitle> </mat-card-header> <mat-card-content> {{ item.tagline }} </mat-card-content> </div> <button *ngIf="item.purchased !== undefined" mat-button color="primary" class="enabled-indicator" (click)="purchasedClicked.emit(item)" [disabled]="item.purchased"> <i *ngIf="!item.purchased" class="material-icons">add</i> <i *ngIf="item.purchased" class="material-icons" style="color: #39B74A">check</i> <span>{{(item.purchased) ? 'ENABLED' : 'ENABLE'}}</span> </button> </div> </mat-card> `, styles: [":host{position:relative}::ng-deep .mat-card-header-text{width:100%}mat-card.hover-card{overflow:hidden;padding:0;margin-bottom:0}mat-card.hover-card mat-card-subtitle,mat-card.hover-card mat-card-title{margin:0 80px 0 0;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}mat-card.hover-card mat-card-subtitle{font-size:12px;margin-top:4px}.hover-area{position:relative;cursor:pointer}.product-banner{max-height:133px;overflow:hidden;display:flex;width:100%;height:100px;align-items:center;justify-content:center}.product-banner img{max-width:100%}@media screen and (min-width:480px){.hover-area{padding-top:55%}.hover-area:hover .description{top:0}.hover-area:hover .enabled-indicator span{width:70px}.product-banner{max-height:none;position:absolute;height:60%;top:0}}.description{box-sizing:border-box;padding:10px 16px 0;overflow:hidden;background:#fff;color:#212121;transition:.3s}.description:after{content:'';display:block;width:100%;height:40%;position:absolute;bottom:0;left:0;background:linear-gradient(rgba(255,255,255,0) 40%,#fff 70%)}mat-card-header{box-sizing:border-box;padding-bottom:16px;margin:0;height:40%;display:flex;align-items:center}mat-card-content{min-height:40px;max-height:80px;padding:16px;margin:0 -16px;border-top:1px solid #e0e0e0;font-size:12px}.enabled-indicator{position:absolute;right:8px;bottom:8px}.enabled-indicator span{display:inline-block;width:70px;overflow:hidden;transition:.3s}@media screen and (min-width:480px){.description{position:absolute;top:60%;width:100%;height:100%}.description:after{height:70%}mat-card-content{min-height:0;max-height:none}.enabled-indicator span{width:0}}button[mat-button]{padding:0 8px;min-width:0}mat-card-actions{position:relative;padding:8px!important;margin:0!important;text-align:right;border-top:1px solid #e0e0e0;background-color:#fff;color:#212121}.status{font-size:14px}.img-container{height:100%;width:100%}"] }] } ]; /** @nocollapse */ StoreCardComponent.ctorParameters = () => [ { type: ImageTransformationService } ]; StoreCardComponent.propDecorators = { item: [{ type: Input }], purchasedClicked: [{ type: Output }], cardClicked: [{ type: Output }], descriptionClicked: [{ type: Output }], bannerClicked: [{ type: Output }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class StoreCardModule { } StoreCardModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, VaIconModule, MatCardModule, MatButtonModule, MatIconModule, MatMenuModule, ImageTransformationModule, VaPricingModule ], declarations: [StoreCardComponent], exports: [StoreCardComponent] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class StoreComponent { constructor() { this.items = []; this.searchable = true; this.itemClicked = new EventEmitter(); } /** * @return {?} */ get filteredItems() { if (this.filterTerm) { return this.items.filter(pkg => pkg.name.toLowerCase().indexOf(this.filterTerm.toLowerCase()) !== -1); } return this.items; } } StoreComponent.decorators = [ { type: Component, args: [{ selector: 'va-store', template: ` <div class="toolbar"> <div *ngIf="searchable" class="table-controls-row"> <va-search-box (update)="filterTerm = $event"></va-search-box> </div> </div> <div class="row row-gutters"> <div *ngFor="let item of filteredItems" class="col-flex"> <va-store-card [item]="item" (cardClicked)="this.itemClicked.emit(item)"></va-store-card> </div> </div> `, styles: [".flex-row{display:flex;flex-direction:row}.toolbar{padding:0;background-color:#fff;color:#616161}.toolbar .disabled{cursor:default}.toolbar .disabled mat-icon{cursor:default;color:#9e9e9e}.toolbar va-search-box{margin-right:10px;width:350px}.toolbar .table-controls-row{padding:0 10px 10px;display:flex;flex-direction:row;align-items:center}.toolbar .table-controls-row:first-of-type{padding-top:10px}.top-border{border-top:1px solid #fff}.selected{background-color:#fff}.row{display:flex;flex-wrap:wrap}.row+.row-gutters{margin-top:0}.row-gutters{margin-top:-20px;margin-left:-20px}.row-gutters>.col-flex{padding-top:20px;padding-left:20px}.col-flex{position:relative;max-width:100%;box-sizing:border-box;flex:0 0 auto;width:100%}@media screen and (min-width:480px){.col-flex{width:50%}}@media screen and (min-width:1200px){.col-flex{width:33.333333%}}"] }] } ]; StoreComponent.propDecorators = { items: [{ type: Input }], searchable: [{ type: Input }], itemClicked: [{ type: Output }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class DropDownFormSectionComponent { constructor() { this.startOpen = false; this.displayAutoTitle = false; this.titleText = ''; this.displayAutoDescription = true; this.descriptionText = ''; this.editingHint = ''; this.expandable = true; this.autoDescriptionText = ''; this.iconName = 'help_outline'; this.subscriptions = []; this.uniqueIndex = 1; this.autoTitleText = ''; } /** * @return {?} */ ngOnInit() { while (this.parentForm.controls.hasOwnProperty(this.titleText + this.uniqueIndex)) { this.uniqueIndex += 1; } this.parentForm.addControl(this.titleText + this.uniqueIndex, this.toFormGroup(this.fields)); /** @type {?} */ const form = (/** @type {?} */ (this.parentForm.controls[this.titleText + this.uniqueIndex])); for (const key in form.controls) { if (form.controls.hasOwnProperty(key)) { if (this.prepopulatedData != null && this.prepopulatedData.hasOwnProperty(key)) { form.controls[key].setValue(this.prepopulatedData[key]); } } } this.subscriptions.push(form.statusChanges.subscribe(change => { /** @type {?} */ let missingFields = false; for (const control in form.controls) { if (!form.controls[control].value || (form.controls[control].value.constructor === Array && !form.controls[control].value[0])) { this.iconName = 'help_outline'; missingFields = true; if (change === 'INVALID') { this.autoTitleText = '* Please fill out all required fields'; if (form.controls[control].dirty || form.controls[control].touched) { this.iconName = 'warning'; break; } } else { this.autoTitleText = 'Optional fields unanswered'; } } } if (!missingFields) { this.iconName = 'check_circle'; this.autoTitleText = 'Complete'; } })); if (this.displayAutoDescription) { this.subscriptions.push(form.valueChanges.subscribe(changes => { /** @type {?} */ let description = ''; for (const key in changes) { if (changes.hasOwnProperty(key) && changes[key] !== null && changes[key].length > 0) { if (changes[key][0].name) { for (const fileKey in changes[key]) { if (changes[key][fileKey] != null) { description += changes[key][fileKey].name; description += ', '; } } } else { description += changes[key]; description += ', '; } } } this.autoDescriptionText = description.substring(0, description.length - 2); })); } form.updateValueAndValidity({ onlySelf: false, emitEvent: true }); } /** * @param {?} formFields * @return {?} */ toFormGroup(formFields) { /** @type {?} */ const group = {}; formFields.forEach(field => { /** @type {?} */ let formControl; if (field.controlType === 'checkbox') { formControl = new FormControl(field.value); } else { formControl = field.required ? new FormControl(field.value, Validators.required) : new FormControl(field.value); } group[field.id] = formControl; this.subscriptions.push(formControl.valueChanges.subscribe(value => (field.value = value))); }); return new FormGroup(group); } /** * @return {?} */ ngOnDestroy() { this.subscriptions.forEach(subscription => subscription.unsubscribe()); } } DropDownFormSectionComponent.decorators = [ { type: Component, args: [{ selector: 'va-dropdown-form-section', template: ` <mat-card *ngIf="!expandable && !(expandable == undefined)" class="not-expandable-card"> <mat-icon class="not-expandable-icon valid"> check_circle</mat-icon> <mat-card-header *ngIf="!displayAutoTitle" class="not-expandable-header"> {{ titleText }} </mat-card-header> <mat-card-header *ngIf="displayAutoTitle" class="not-expandable-header valid"> <div class="title"> {{ titleText }}</div> <div *ngIf="titleText && autoTitleText">&nbsp;</div> <ng-container><i> Complete </i></ng-container> </mat-card-header> <mat-panel-description class="not-expandable-description"> {{ descriptionText }} </mat-panel-description> </mat-card> <mat-expansion-panel *ngIf="expandable || expandable == undefined" [expanded]="startOpen"> <mat-expansion-panel-header> <div class="dropdown-form-header"> <mat-icon [ngClass]="{invalid: iconName=='warning', valid: iconName=='check_circle', question: iconName=='help_outline'}"> {{ iconName }} </mat-icon> <mat-panel-title *ngIf="!displayAutoTitle"> {{ titleText }} </mat-panel-title> <mat-panel-title *ngIf="displayAutoTitle" [ngClass]="{valid: iconName=='check_circle', invalid: iconName=='warning'}"> <div class="title"> {{ titleText }}</div> <div *ngIf="titleText && autoTitleText"> &nbsp;</div> <ng-container><i> {{autoTitleText}} </i></ng-container> </mat-panel-title> <mat-panel-description *ngIf="displayAutoDescription" [ngClass]="{invalid: iconName=='warning'}"> {{ autoDescriptionText }} </mat-panel-description> <mat-panel-description *ngIf="!displayAutoDescription" [ngClass]="{invalid: iconName=='warning'}"> <i> {{descriptionText}} </i> </mat-panel-description> </div> </mat-expansion-panel-header> <div class="expansion-panel-body"> <va-field *ngFor="let field of fields" [field]="field" [form]="parentForm.controls[titleText + uniqueIndex]"></va-field> <p *ngIf="editingHint != ''" class="editing-hint"><i>{{ editingHint }}</i></p> </div> </mat-expansion-panel> `, styles: [":host-context(va-dropdown-form-section){font-size:14px}:host-context(va-dropdown-form-section) .expansion-panel-body{margin-top:-10px;display:block;width:60%}.mat-expanded,.mat-expansion-panel{transition:margin .4s}.dropdown-form-header{width:100%;display:flex}mat-panel-description{align-self:center;flex:inherit;display:initial;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}mat-panel-description.invalid{color:#c62828}mat-icon{margin-right:7px;-ms-grid-row-align:center;align-self:center}mat-icon.valid{color:#4caf50}mat-icon.invalid{color:#c62828}mat-icon.question{color:#9e9e9e}mat-panel-title{-ms-grid-row-align:center;align-self:center;flex:none;margin-right:20px}mat-panel-title.valid{color:#4caf50}mat-panel-title.invalid{color:#c62828}.editing-hint{color:#9e9e9e}.title{color:#212121}.not-expandable-card{background:#fff;cursor:default;font-size:15px;display:flex;height:48px;align-items:center;box-shadow:0 3px 1px -2px rgba(0,0,0,.2),0 2px 2px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.not-expandable-card .not-expandable-icon{margin-left:24px}.not-expandable-card .not-expandable-header{display:flex}.not-expandable-card .not-expandable-description{margin-left:22px}.not-expandable-card .valid{color:#4caf50}"] }] } ]; /** @nocollapse */ DropDownFormSectionComponent.ctorParameters = () => []; DropDownFormSectionComponent.propDecorators = { prepopulatedData: [{ type: Input }], startOpen: [{ type: Input }], parentForm: [{ type: Input }], displayAutoTitle: [{ type: Input }], titleText: [{ type: Input }], displayAutoDescription: [{ type: Input }], descriptionText: [{ type: Input }], fields: [{ type: Input }], editingHint: [{ type: Input }], expandable: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class FieldComponent { /** * @return {?} */ get isValid() { return this.form.controls[this.field.id].valid || this.form.controls[this.field.id].pristine; } } FieldComponent.decorators = [ { type: Component, args: [{ selector: 'va-field', template: "<ng-container [ngSwitch]=\"field.controlType\" [formGroup]=\"form\">\n\n <ng-container *ngSwitchCase=\"'dropdown'\">\n <mat-form-field class=\"form-field-container\">\n <mat-select matInput [formControlName]=\"field.id\" [id]=\"field.id\" [placeholder]=\"field.getLabel()\" [disabled]=\"field.disabled\">\n <mat-option *ngFor=\"let option of field.options\" [value]=\"option.value\" [disabled]=\"option.disabled\">{{ option.label }}</mat-option>\n </mat-select>\n <mat-hint *ngIf=\"field.description\"> {{ field.description }} </mat-hint>\n </mat-form-field>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'textbox'\">\n <mat-form-field class=\"form-field-container\">\n <span matPrefix *ngIf=\"field.prefix\"> {{ field.prefix}} &nbsp;</span>\n <input matInput [type]=\"field.textboxType\" [id]=\"field.id\" [formControlName]=\"field.id\" [placeholder]=\"field.getLabel()\" [pattern]=\"field.regexValidator || ''\" [readonly]=\"field.disabled\">\n <span matSuffix *ngIf=\"field.suffix\"> &nbsp;{{ field.suffix }} </span>\n <mat-hint *ngIf=\"field.description\"> {{ field.description }} </mat-hint>\n <mat-error *ngIf=\"form.controls[field.id].hasError('pattern')\"> {{field.regexErrorMessage}} </mat-error>\n </mat-form-field>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'checkbox'\">\n <div class=\"form-field-container checkbox\">\n <mat-checkbox [formControlName]=\"field.id\" [id]=\"field.id\" ngControlDefault [disabled]=\"field.disabled\"></mat-checkbox>\n <div class=\"checkbox-placeholder\"> {{field.label}}</div>\n </div>\n <div class=\"checkbox-description\" *ngIf=\"field.description\">{{ field.description }}</div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'textarea'\">\n <mat-form-field class=\"form-field-container\">\n <textarea matInput [formControlName]=\"field.id\" [placeholder]=\"field.getLabel()\" [id]=\"field.id\" [readonly]=\"field.disabled\"></textarea>\n <mat-hint *ngIf=\"field.description\"> {{ field.description }} </mat-hint>\n </mat-form-field>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'file'\">\n <div class=\"form-field-container\">\n <file-group-uploader [uploadUrl]=\"field.uploadUrl\" [label]=\"field.getLabel()\" [description]=\"field.description\" [formGroup]=\"form\" [formControlName]=\"field.id\" [disabled]=\"field.disabled\"\n [numFiles]=\"field.numFiles\" class=\"form-field-container\"></file-group-uploader>\n </div>\n </ng-container>\n\n <ng-container *ngSwitchCase=\"'vbcuser'\">\n <mat-form-field class=\"form-field-container\">\n <mat-select matInput [formControlName]=\"field.id\" [id]=\"field.id\" [placeholder]=\"field.getLabel()\" [disabled]=\"field.disabled\">\n <mat-option *ngFor=\"let option of field.options\" [value]=\"option.value\" [disabled]=\"option.disabled\">{{ option.label }}</mat-option>\n </mat-select>\n <mat-hint *ngIf=\"field.description\"> {{ field.description }} </mat-hint>\n </mat-form-field>\n </ng-container>\n\n</ng-container>\n", styles: [".form-field-container{font-size:16px;width:100%;margin:10px 0}.checkbox{display:flex;padding:5px 0;color:rgba(0,0,0,.54);margin-top:24px}.checkbox .checkbox-placeholder{margin-left:10px}.checkbox-description{margin-top:-14px;margin-left:32px;color:rgba(0,0,0,.54);font-size:12px}"] }] } ]; FieldComponent.propDecorators = { field: [{ type: Input }], form: [{ type: Input }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class FieldService { /** * @param {?} field * @return {?} */ createFormControl(field) { if (field.required) { return new FormControl(field.value, Validators.required); } else { return new FormControl(field.value); } } /** * @param {?} formFields * @param {?} subscriptions * @return {?} */ toFormGroup(formFields, subscriptions) { /** @type {?} */ const group = {}; formFields.forEach(field => { /** @type {?} */ let formControl; if (field.controlType === 'checkbox') { formControl = new FormControl(field.value); } else { formControl = this.createFormControl(field); } group[field.id] = formControl; if (subscriptions) { subscriptions.push(formControl.valueChanges.subscribe(value => (field.value = value))); } }); return new FormGroup(group); } } FieldService.decorators = [ { type: Injectable } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class VaHeaderContainerComponent { constructor() { this.loaded = true; this.showEnableAddon = false; this.prerequisiteSelected = new EventEmitter(); this.actionSelected = new EventEmitter(); this.pricingActionSelected = new EventEmitter(); } /** * @return {?} */ onActionSelected() { this.actionSelected.emit(); } /** * @return {?} */ onPrerequisiteSelected() { this.prerequisiteSelected.emit(); } /** * @param {?} billedProduct * @return {?} */ showPricingModel(billedProduct) { return billedProduct && [BillingModel.Tiered, BillingModel.Stairstep].includes(billedProduct.billingModel); } /** * @param {?} billingModel * @return {?} */ getPricingModelTooltips(billingModel) { switch (billingModel) { case BillingModel.Stairstep: return 'Price applies per unit within a range'; case BillingModel.Tiered: return 'Price applies to all units once range is met'; default: return ''; } } } VaHeaderContainerComponent.decorators = [ { type: Component, args: [{ selector: 'va-header-container', template: "<div class=\"header-container\">\n <div class=\"product-overview\">\n <div class=\"product-id\">\n\n <va-icon [iconUrl]=\"iconUrl\" [name]=\"title\" [diameter]=\"120\"></va-icon>\n\n <div class=\"product-id-text\">\n\n <h1 [ngClass]=\"{'stencil-title stencil-shimmer': !title, 'product-title': title}\">\n {{ title }}\n </h1>\n\n <span [ngClass]=\"{'stencil-tagline stencil-shimmer': !title && !tagline, 'tagline': tagline}\">\n {{ tagline }}\n </span>\n\n <div *ngIf=\"prerequisite\" class=\"prerequisite\">\n <span class=\"requires-text\">Requires </span>\n <span>{{ prerequisite }}</span>\n </div>\n\n <mat-chip-list>\n <mat-chip *ngFor=\"let chip of chipLabels\">{{ chip }}</mat-chip>\n </mat-chip-list>\n\n </div>\n </div>\n\n <div class=\"pricing\">\n\n <button *ngIf=\"showAction && !showEnableAddon\" mat-raised-button class=\"app-enable-button\" [disabled]=\"!actionEnabled\" (click)=\"onActionSelected()\">\n {{ actionLabel }}\n </button>\n\n <button *ngIf=\"showEnableAddon && !showAction\" mat-raised-button class=\"app-enable-button\" [disabled]=\"!showEnableAddon\" (click)=\"onActionSelected()\">\n {{ actionLabel }}\n </button>\n\n <div *ngIf=\"prerequisiteLabel && !showEnableAddon\" class=\"addon-enable\" (click)=\"onPrerequisiteSelected()\">\n Enabled with {{prerequisiteLabel}}\n </div>\n\n <div *ngIf=\"showPricing\" class=\"price-box\">\n <div *ngIf=\"pricing?.prices?.length\" class=\"wholesale-price\">{{ pricingLabel }}\n <div *ngIf=\"showPricingModel(billedProduct) && hasVerifiedContract\"\n class=\"pricing-model\"\n matTooltip=\"{{getPricingModelTooltips(billedProduct.billingModel)}}\">({{ billedProduct.billingModel }})\n </div>\n </div>\n <va-pricing [pricing]=\"pricing\"\n [billedProduct]=\"billedProduct\"\n [hasVerifiedContract]=\"hasVerifiedContract\"\n [loaded]=\"loaded\"></va-pricing>\n </div>\n\n <p *ngIf=\"pricingActionEnabled\" class=\"pricing-action-container\">\n <a (click)=\"pricingActionSelected.emit()\">{{ pricingActionLabel }}</a>\n </p>\n\n </div>\n\n </div>\n</div>\n", styles: [":host{font-size:16px}.product-title{font-size:24px}.product-overview{display:flex;flex-wrap:wrap;border-bottom:1px solid #e0e0e0}.product-overview h1{margin:0;font-weight:400;line-height:1.2}.product-overview mat-chip{margin-bottom:3px}.product-id{display:flex;width:100%;padding:24px;flex-grow:1}.product-id va-icon{margin-right:20px}@media screen and (max-width:600px){.product-id va-icon ::ng-deep .va-icon-container{width:40px!important;height:40px!important}.product-id va-icon ::ng-deep .va-icon-container span{line-height:40px!important;font-size:15px!important}}.product-id .product-id-text{display:flex;flex-direction:column;justify-content:center}.product-id span{overflow:hidden}.product-id .tagline{color:#616161;margin:.5em 0 1em;font-size:16px}@media screen and (min-width:600px){.product-title{font-size:32px}.product-id .tagline{font-size:20px}.product-id{width:66%;padding-right:20px}}.product-id .prerequisite{margin:.5em 0 1em;font-size:14px}.product-id .requires-text{color:#9e9e9e}.inline-link{display:inline-flex!important;color:#1e88e5;cursor:pointer;padding-top:5px}.pricing{display:flex;flex-direction:column;width:100%;padding:0 24px 24px;color:#616161}@media screen and (min-width:600px){.pricing{width:34%;padding-top:24px;padding-left:20px}}.pricing span{display:block}.pricing span:nth-child(2){margin:.5em 0 1em}.pricing .price{font-size:24px;font-weight:700;line-height:1;color:#4caf50}.pricing .price-box{padding-top:10px}.pricing .pricing-action-container{margin:8px 0}.pricing .pricing-action-container mat-icon{vertical-align:middle;margin-right:6px}.pricing .pricing-action-container a{cursor:pointer}.pricing .pricing-model{display:inline-block;font-size:.8rem;color:#9e9e9e;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.app-enable-button{height:inherit;color:#fff;background-color:#4caf50;font-size:20px;padding:14px 16px;line-height:1}.addon-enable{height:inherit;background-color:#e0e0e0;font-size:14px;padding:14px 16px;line-height:1;color:#1e88e5;cursor:pointer;text-align:center}.wholesale-price{margin-top:0;padding-bottom:8px;margin-bottom:4px;border-bottom:1px solid #e0e0e0}.stencil-title{height:32px;width:200px;margin-bottom:5px!important}.stencil-tagline{height:32px;width:250px}"] }] } ]; VaHeaderContainerComponent.propDecorators = { iconUrl: [{ type: Input }], title: [{ type: Input }], tagline: [{ type: Input }], prerequisite: [{ type: Input }], chipLabels: [{ type: Input }], pricing: [{ type: Input }], billedProduct: [{ type: Input }], loaded: [{ type: Input }], hasVerifiedContract: [{ type: Input }], pricingLabel: [{ type: Input }], pricingActionLabel: [{ type: Input }], pricingActionEnabled: [{ type: Input }], actionEnabled: [{ type: Input }], actionLabel: [{ type: Input }], showAction: [{ type: Input }], showPricing: [{ type: Input }], showEnableAddon: [{ type: Input }], prerequisiteLabel: [{ type: Input }], prerequisiteSelected: [{ type: Output }], actionSelected: [{ type: Output }], pricingActionSelected: [{ type: Output }] }; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class VaHeaderContainerModule { } VaHeaderContainerModule.decorators = [ { type: NgModule, args: [{ imports: [ CommonModule, MatIconModule, VaPricingModule, VaIconModule, MatChipsModule, MatButtonModule, MatTooltipModule, ], declarations: [VaHeaderContainerComponent], exports: [VaHeaderContainerComponent] },] } ]; /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class OrderFormSectionData { } /** * @fileoverview added by tsickle * @suppress {checkTypes,extraRequire,missingReturn,unusedPrivateMembers,uselessCode} checked by tsc */ class OrderFormSectionComponent { constructor() { } /** * @return {?} */ ngOnInit() { // The first section should always be expanded. /** @type {?} */ const topSection = this.getTopSection(); if (topSection) { topSection.startOpen = true; } } /** * @return {?} */ getTopSection() { if (!this.data) { return null; } if (this.data.primarySection) { return this.data.primarySection; } if (this.data.subsections && this.data.subsections.length > 0) { return this.data.subsections[0]; } return null; } } OrderFormSectionComponent.decorators = [ { type: Component, args: [{ selector: 'va-order-form-section', template: "<div>\n <mat-card class=\"va-order-form-section-header mat-card\">\n <div class=\"product-overview\">\n <div class=\"product-id\">\n <va-icon [iconUrl]=\"data.iconUrl\" [name]=\"data.titleText\" [diameter]=\"45\"></va-icon>\n <div class=\"product-id-text\">\n <h1 class=\"product-title\">{{ data.titleText }}</h1>\n <span *ngIf=\"data.subtitleText\" class=\"tagline\">{{ data.subtitleText }}</span>\n </div>\n </div>\n <div class=\"description\" *ngIf=\"data.descriptionText\">\n <span>{{ data.descriptionText }}</span>\n </div>\n </div>\n </mat-card>\n <va-dropdown-form-section class=\"va-primary-form-section\" *ngIf=\"data.primarySection\"\n [titleText]=\"data.primarySection.titleText\"\n [editingHint]=\"data.primarySection.editingHint\"\n [displayAutoDescription]=\"data.primarySection.displayAutoDescription\"\n [descriptionText]=\"data.primarySection.descriptionText\"\n [displayAutoTitle]=\"data.primarySection.displayAutoTitle\"\n [prepopulatedData]=\"data.primarySection.prepopulatedData\"\n [parentForm]=\"data.parentForm\"\n [fields]=\"data.primarySection.fields\"\n [startOpen]=\"data.primarySection.startOpen\"\n [expandable]=\"data.primarySection.expandable\">\n </va-dropdown-form-section>\n <ng-container *ngIf=\"data.subsections?.length > 0\">\n <va-dropdown-form-section class=\"va-secondary-form-section\" *ngFor=\"let addon of data.subsections\"\n [titleText]=\"addon.titleText\"\n [editingHint]=\"addon.editingHint\"\n [displayAutoDescription]=\"addon.displayAutoDescription\"\n [descriptionText]=\"addon.descriptionText\"\n [displayAutoTitle]=\"addon.displayAutoTitle\"\n [prepopulatedData]=\"addon.prepopulatedData\"\n [parentForm]=\"data.parentForm\"\n [fields]=\"addon.fields\"\n [startOpen]=\"addon.startOpen\"\n [expandable]=\"addon.expandable\">\n </va-dropdown-form-section>\n </ng-container>\n <ng-container *ngIf=\"!data.primarySection && !(data.subsections?.len