Merge remote-tracking branch 'origin/dev' into test
commit
e5f48b1e23
|
@ -50,7 +50,7 @@ export class DeviceDisplayComponent implements OnInit {
|
|||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.device.pantacorUpdateId) {
|
||||
if (!this.device.pantacorConfig.deploymentId) {
|
||||
this.softwareUpdateText = 'NO UPDATES AVAILABLE';
|
||||
this.softwareUpdateDisabled = true;
|
||||
} else {
|
||||
|
@ -64,6 +64,17 @@ export class DeviceDisplayComponent implements OnInit {
|
|||
return knownPlatform ? knownPlatform.displayName : device.platform;
|
||||
}
|
||||
|
||||
applySoftwareUpdate() {
|
||||
this.deviceService.applySoftwareUpdate(this.device.pantacorConfig.deploymentId).subscribe({
|
||||
next: () => {
|
||||
this.openSuccessSnackbar();
|
||||
this.softwareUpdateText = 'NO UPDATES AVAILABLE';
|
||||
this.softwareUpdateDisabled = true;
|
||||
},
|
||||
error: () => { this.openErrorSnackbar(); }
|
||||
});
|
||||
}
|
||||
|
||||
openErrorSnackbar() {
|
||||
const config = new MatSnackBarConfig();
|
||||
config.data = {type: 'error', message: 'An error occurred, device will not be updated.'};
|
||||
|
@ -77,16 +88,4 @@ export class DeviceDisplayComponent implements OnInit {
|
|||
this.snackbar.openFromComponent(SnackbarComponent, config);
|
||||
}
|
||||
|
||||
applySoftwareUpdate() {
|
||||
this.deviceService.applySoftwareUpdate(this.device.pantacorUpdateId).subscribe(
|
||||
() => {
|
||||
this.openSuccessSnackbar();
|
||||
this.softwareUpdateText = 'NO UPDATES AVAILABLE';
|
||||
this.softwareUpdateDisabled = true;
|
||||
|
||||
},
|
||||
() => { this.openErrorSnackbar(); }
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ and limitations under the License.
|
|||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { UntypedFormGroup } from '@angular/forms';
|
||||
import { OptionButtonsConfig } from '@account/models/option-buttons-config.model';
|
||||
import { environment } from '@account/environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'account-software-release-card',
|
||||
|
@ -29,8 +30,14 @@ export class SoftwareReleaseCardComponent implements OnInit {
|
|||
public releaseChannelConfig: OptionButtonsConfig;
|
||||
|
||||
constructor() {
|
||||
let channels: string[];
|
||||
if (environment.production) {
|
||||
channels = ['Beta', 'Stable', 'LTS'];
|
||||
} else {
|
||||
channels = ['Development', 'Beta QA', 'Stable QA', 'LTS QA'];
|
||||
}
|
||||
this.releaseChannelConfig = {
|
||||
options: ['Stable', 'Latest', 'QA'],
|
||||
options: channels,
|
||||
buttonWidth: '130px'
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
<div fxLayout="row wrap" fxLayoutAlign="center" fxLayoutGap.gt-xs="16px">
|
||||
<mat-card *ngFor="let device of devices; let i = index">
|
||||
<mat-card-title fxLayout="row" fxLayoutAlign="start center">
|
||||
<img [src]="getDeviceIcon(device)"/>
|
||||
{{device.name}}
|
||||
</mat-card-title>
|
||||
<mat-card-header>
|
||||
<img mat-card-avatar [src]="getDeviceIcon(device)"/>
|
||||
<mat-card-title class="mat-h1">{{device.name}}</mat-card-title>
|
||||
</mat-card-header>
|
||||
<ng-container [ngSwitch]="device.status">
|
||||
<mat-card-subtitle
|
||||
class="status-active"
|
||||
|
|
|
@ -18,19 +18,15 @@
|
|||
|
||||
@use "@angular/material" as mat;
|
||||
@use 'components/buttons' as buttons;
|
||||
@use 'components/cards' as cards;
|
||||
@use 'mycroft-theme' as theme;
|
||||
|
||||
@mixin panel-defaults {
|
||||
border-radius: 12px;
|
||||
width: 330px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
#add-device-button {
|
||||
@include buttons.action-button-primary;
|
||||
@include panel-defaults;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
height: 50px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 362px;
|
||||
min-width: 350px;
|
||||
|
||||
|
@ -51,37 +47,28 @@
|
|||
|
||||
}
|
||||
|
||||
#edit-button {
|
||||
@include buttons.action-button-primary
|
||||
}
|
||||
|
||||
#remove-button {
|
||||
@include buttons.action-button-warn
|
||||
}
|
||||
|
||||
mat-card {
|
||||
@include panel-defaults;
|
||||
|
||||
button {
|
||||
margin-bottom: 8px;
|
||||
margin-left: 8px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
mat-card-title {
|
||||
color: mat.get-color-from-palette(theme.$mycroft-primary, 500);
|
||||
font-weight: bold;
|
||||
@include cards.selene-card;
|
||||
width: 330px;
|
||||
|
||||
mat-card-header {
|
||||
img {
|
||||
height: 60px;
|
||||
margin-right: 16px;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.mat-h1 {
|
||||
color: mat.get-color-from-palette(theme.$mycroft-primary, 500);
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
mat-card-subtitle {
|
||||
border-radius: 4px;
|
||||
padding: 4px;
|
||||
font-weight: bold;
|
||||
margin-top: 4px;
|
||||
padding: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
@ -110,3 +97,12 @@ mat-card {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#edit-button {
|
||||
@include buttons.action-button-primary
|
||||
}
|
||||
|
||||
#remove-button {
|
||||
@include buttons.action-button-warn
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,16 @@
|
|||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p class="mat-body">
|
||||
You have chosen not to subscribe to Mycroft's membership program.
|
||||
You are not subscribed to Mycroft's membership program.
|
||||
<p class="mat-body">
|
||||
Mycroft supporters
|
||||
ensure we can keep the services open and available. Your credit card information is
|
||||
sent to the payment service and not stored on Mycroft's servers.
|
||||
If would like to become a member, select one of the below options.
|
||||
Supporting Mycroft with a membership helps keep Mycroft's services open and available.
|
||||
</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<a mat-button>$1.99 USD MONTHLY</a>
|
||||
<a mat-button>$19.99 USD YEARLY</a>
|
||||
<button mat-button *ngFor="let membershipType of membershipTypes" (click)="openPaymentDialog(membershipType)">
|
||||
{{membershipType.rate + '/' + membershipType.ratePeriod.toUpperCase() + ' (USD)'}}
|
||||
</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
||||
|
@ -28,10 +28,14 @@
|
|||
Proudly supporting Mycroft for {{accountMembership.duration}}!
|
||||
</p>
|
||||
<p *ngIf="!accountMembership.duration" class="mat-body">
|
||||
Thank you for supporting Mycroft!
|
||||
Thank you for supporting Mycroft with your membership!
|
||||
</p>
|
||||
<p class="mat-body">
|
||||
You can cancel your membership at any time. You can also update your credit card
|
||||
information.
|
||||
</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<a mat-button color="warn">CANCEL MEMBERSHIP</a>
|
||||
<a mat-button id="cancel-button" (click)="openCancelConfirmDialog()">CANCEL MEMBERSHIP</a>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
|
|
@ -16,11 +16,19 @@
|
|||
// and limitations under the License.
|
||||
// *****************************************************************************
|
||||
|
||||
@use "@angular/material" as mat;
|
||||
@use "components/buttons" as buttons;
|
||||
@use "components/cards" as cards;
|
||||
@use "mycroft-theme" as theme;
|
||||
|
||||
mat-card {
|
||||
@include cards.selene-card;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 800px;
|
||||
|
||||
#cancel-button {
|
||||
@include buttons.action-button-warn
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,10 @@ See the Apache Version 2.0 License for specific language governing permissions
|
|||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
import { Component, Input, OnDestroy } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core';
|
||||
import { MediaChange, MediaObserver } from '@angular/flex-layout';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
|
||||
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { faCreditCard, IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
||||
|
@ -27,8 +28,9 @@ import { AccountMembership } from '@account/models/account-membership.model';
|
|||
import { MembershipType } from '@account/models/membership.model';
|
||||
import { MembershipUpdate } from '@account/models/membership-update.model';
|
||||
import { ProfileService } from '@account/http/profile.service';
|
||||
|
||||
const twoSeconds = 2000;
|
||||
import { MembershipCancelConfirmComponent } from '@account/app/modules/profile/components/modals/membership-cancel-confirm/membership-cancel-confirm.component';
|
||||
import { PaymentComponent } from '@account/app/modules/profile/components/views/payment/payment.component';
|
||||
import { SnackbarComponent } from 'shared';
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -39,12 +41,15 @@ const twoSeconds = 2000;
|
|||
export class MembershipComponent implements OnDestroy {
|
||||
@Input() accountMembership: AccountMembership;
|
||||
@Input() membershipTypes: MembershipType[];
|
||||
@Output() membershipUpdated = new EventEmitter();
|
||||
public alignVertical: boolean;
|
||||
private mediaWatcher: Subscription;
|
||||
public membershipIcon: IconDefinition = faCreditCard;
|
||||
|
||||
constructor(
|
||||
public cancelConfirmDialog: MatDialog,
|
||||
public mediaObserver: MediaObserver,
|
||||
public paymentDialog: MatDialog,
|
||||
private profileService: ProfileService,
|
||||
private snackbar: MatSnackBar
|
||||
) {
|
||||
|
@ -61,17 +66,72 @@ export class MembershipComponent implements OnDestroy {
|
|||
this.mediaWatcher.unsubscribe();
|
||||
}
|
||||
|
||||
updateAccount(membershipUpdate: MembershipUpdate) {
|
||||
const accountUpdate = {membership: membershipUpdate};
|
||||
this.profileService.updateAccount(accountUpdate).subscribe(
|
||||
() => {
|
||||
this.snackbar.open(
|
||||
'Membership updated',
|
||||
null,
|
||||
{panelClass: 'mycroft-no-action-snackbar', duration: twoSeconds}
|
||||
);
|
||||
openPaymentDialog(membershipType: MembershipType) {
|
||||
const dialogConfig = new MatDialogConfig();
|
||||
dialogConfig.data = {newAccount: false, membershipType: membershipType};
|
||||
dialogConfig.disableClose = true;
|
||||
dialogConfig.maxWidth = 520;
|
||||
dialogConfig.restoreFocus = true;
|
||||
const dialogRef = this.paymentDialog.open(PaymentComponent, dialogConfig);
|
||||
dialogRef.afterClosed().subscribe(
|
||||
(stripeToken) => {
|
||||
const membershipUpdate: MembershipUpdate = {
|
||||
action: 'add',
|
||||
membershipType: membershipType.type,
|
||||
paymentMethod: 'Stripe',
|
||||
paymentToken: stripeToken
|
||||
};
|
||||
if (this.accountMembership) {
|
||||
membershipUpdate.action = 'update';
|
||||
}
|
||||
this.updateAccount(membershipUpdate);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
openCancelConfirmDialog() {
|
||||
const dialogConfig = new MatDialogConfig();
|
||||
dialogConfig.disableClose = true;
|
||||
dialogConfig.maxWidth = 520;
|
||||
dialogConfig.restoreFocus = true;
|
||||
const dialogRef = this.cancelConfirmDialog.open(MembershipCancelConfirmComponent, dialogConfig);
|
||||
dialogRef.afterClosed().subscribe(
|
||||
(cancelled) => {
|
||||
if (cancelled) {
|
||||
const membershipUpdate: MembershipUpdate = { action: 'cancel' };
|
||||
this.updateAccount(membershipUpdate);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateAccount(membershipUpdate: MembershipUpdate) {
|
||||
const accountUpdate = {membership: membershipUpdate};
|
||||
const config = new MatSnackBarConfig();
|
||||
let successMessage: string;
|
||||
let errorMessage: string;
|
||||
|
||||
if (membershipUpdate.action === 'add') {
|
||||
successMessage = 'Membership added. Thank you!';
|
||||
errorMessage = 'Failed to add membership - contact support';
|
||||
} else if (membershipUpdate.action === 'cancel') {
|
||||
successMessage = 'Membership cancelled';
|
||||
errorMessage = 'Failed to cancel membership - contact support';
|
||||
} else {
|
||||
successMessage = 'Payment information updated';
|
||||
errorMessage = 'Failed to update payment information - contact support';
|
||||
}
|
||||
|
||||
this.profileService.updateAccount(accountUpdate).subscribe({
|
||||
next: () => {
|
||||
this.membershipUpdated.emit();
|
||||
config.data = {type: 'info', message: successMessage};
|
||||
this.snackbar.openFromComponent(SnackbarComponent, config);
|
||||
},
|
||||
error: () => {
|
||||
config.data = {type: 'error', message: errorMessage};
|
||||
this.snackbar.openFromComponent(SnackbarComponent, config);
|
||||
}}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<mat-button-toggle-group
|
||||
class="options-button-group"
|
||||
[vertical]="alignVertical"
|
||||
[(ngModel)]="selectedMembershipType"
|
||||
(change)="onMembershipSelect($event)"
|
||||
>
|
||||
<mat-button-toggle
|
||||
*ngFor="let membership of membershipTypes"
|
||||
[value]="membership.type"
|
||||
>
|
||||
<h3 class="mat-h3">{{membership.type}}</h3>
|
||||
<p class="mat-body" *ngIf="membership.type != 'Maybe Later'">${{membership.rate}} USD</p>
|
||||
</mat-button-toggle>
|
||||
<mat-button-toggle [value]="'Maybe Later'">
|
||||
<h3 id="non-supporter" class="mat-h3">Maybe Later</h3>
|
||||
</mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
|
@ -1,127 +0,0 @@
|
|||
/*! *****************************************************************************
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
|
||||
Copyright (c) Mycroft AI Inc. All rights reserved.
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
|
||||
MERCHANTABLITY OR NON-INFRINGEMENT.
|
||||
|
||||
See the Apache Version 2.0 License for specific language governing permissions
|
||||
and limitations under the License.
|
||||
***************************************************************************** */
|
||||
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { MediaChange, MediaObserver } from '@angular/flex-layout';
|
||||
import { MatButtonToggleChange } from '@angular/material/button-toggle';
|
||||
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { AccountMembership } from '@account/models/account-membership.model';
|
||||
import { MembershipType } from '@account/models/membership.model';
|
||||
import { ProfileService } from '@account/http/profile.service';
|
||||
import { PaymentComponent } from '@account/app/modules/profile/components/views/payment/payment.component';
|
||||
import { MembershipUpdate } from '@account/models/membership-update.model';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'account-membership-options',
|
||||
templateUrl: './membership-options.component.html',
|
||||
styleUrls: ['./membership-options.component.scss']
|
||||
})
|
||||
export class MembershipOptionsComponent implements OnInit, OnDestroy {
|
||||
@Input() accountMembership: AccountMembership;
|
||||
@Input() membershipTypes: MembershipType[];
|
||||
@Output() membershipChange = new EventEmitter<MembershipUpdate>();
|
||||
public alignVertical: boolean;
|
||||
public mediaWatcher: Subscription;
|
||||
public selectedMembershipType: string;
|
||||
|
||||
constructor(
|
||||
public mediaObserver: MediaObserver,
|
||||
public paymentDialog: MatDialog,
|
||||
) {
|
||||
this.mediaWatcher = mediaObserver.asObservable().subscribe(
|
||||
(change: MediaChange[]) => {
|
||||
change.forEach((item) => {
|
||||
this.alignVertical = ['xs', 'sm'].includes(item.mqAlias);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setSelectedMembershipType();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.mediaWatcher.unsubscribe();
|
||||
}
|
||||
|
||||
setSelectedMembershipType() {
|
||||
let selectedMembership: MembershipType;
|
||||
if (this.accountMembership) {
|
||||
selectedMembership = this.membershipTypes.find(
|
||||
(membershipType) => membershipType.type === this.accountMembership.type
|
||||
);
|
||||
this.selectedMembershipType = selectedMembership.type;
|
||||
} else {
|
||||
this.selectedMembershipType = 'Maybe Later';
|
||||
}
|
||||
}
|
||||
|
||||
onMembershipSelect(membershipType: MatButtonToggleChange) {
|
||||
const selectedMembership = this.membershipTypes.find(
|
||||
(membership) => membership.type === membershipType.value
|
||||
);
|
||||
let membershipUpdate;
|
||||
if (selectedMembership) {
|
||||
if (this.accountMembership) {
|
||||
// We have the user's credit card info but they decide to change plans
|
||||
membershipUpdate = {
|
||||
paymentMethod: 'Stripe',
|
||||
newMembership: false,
|
||||
membershipType: membershipType
|
||||
};
|
||||
this.membershipChange.emit(membershipUpdate);
|
||||
} else {
|
||||
// No credit card info. Go to payment dialog to collect
|
||||
this.openPaymentDialog(membershipType.value);
|
||||
}
|
||||
} else {
|
||||
// Membership termination
|
||||
membershipUpdate = {newMembership: false, membershipType: null};
|
||||
this.membershipChange.emit(membershipUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
openPaymentDialog(membershipType: string) {
|
||||
const dialogConfig = new MatDialogConfig();
|
||||
dialogConfig.data = {newAccount: false, membershipType: membershipType};
|
||||
dialogConfig.disableClose = true;
|
||||
dialogConfig.restoreFocus = true;
|
||||
const dialogRef = this.paymentDialog.open(PaymentComponent, dialogConfig);
|
||||
dialogRef.afterClosed().subscribe(
|
||||
(stripeToken) => {
|
||||
if (stripeToken) {
|
||||
const membershipUpdate: MembershipUpdate = {
|
||||
newMembership: true,
|
||||
membershipType: membershipType,
|
||||
paymentMethod: 'Stripe',
|
||||
paymentToken: stripeToken
|
||||
};
|
||||
this.membershipChange.emit(membershipUpdate);
|
||||
} else {
|
||||
this.setSelectedMembershipType();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<h1 mat-dialog-title>Are you sure?</h1>
|
||||
<mat-dialog-content>
|
||||
<p class="mat-body">
|
||||
You know how this goes. You click a red button, we make sure you meant to do it.
|
||||
</p>
|
||||
<p class="mat-body">
|
||||
Your membership supports continued development of an open, privacy focused voice assistant.
|
||||
We hope you are seeing this as a result of an unintended mouse click (those buttons can be
|
||||
touchy!). If so, click the "cancel" button below.
|
||||
</p>
|
||||
<p class="mat-body">
|
||||
If you really do want to cancel your membership, that's a bummer. You can re-activate your
|
||||
membership at any time. Click the "confirm" button below and we will cancel your
|
||||
subscription immediately.
|
||||
</p>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button id="confirm-button" mat-button (click)="onConfirm()">CONFIRM</button>
|
||||
<button id="cancel-button" mat-button (click)="onCancel()">CANCEL</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -15,32 +15,15 @@
|
|||
// See the Apache Version 2.0 License for specific language governing permissions
|
||||
// and limitations under the License.
|
||||
// *****************************************************************************
|
||||
.options-button-group {
|
||||
border: none;
|
||||
padding: 16px;
|
||||
|
||||
.mat-button-toggle {
|
||||
background-color: #e6e8ea;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
height: 80px;
|
||||
margin: 8px;
|
||||
padding: 8px;
|
||||
width: 200px;
|
||||
@use "@angular/material" as mat;
|
||||
@use "components/buttons" as buttons;
|
||||
@use "mycroft-theme" as theme;
|
||||
|
||||
.mat-h3 {
|
||||
font-weight: bold;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#non-supporter {
|
||||
margin-top: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-button-toggle-checked {
|
||||
background-color: #22a7f0;
|
||||
color: white;
|
||||
}
|
||||
h1 {
|
||||
color: mat.get-color-from-palette(theme.$mycroft-warn, 400);
|
||||
}
|
||||
|
||||
#confirm-button {
|
||||
@include buttons.action-button-warn;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'account-membership-cancel-confirm',
|
||||
templateUrl: './membership-cancel-confirm.component.html',
|
||||
styleUrls: ['./membership-cancel-confirm.component.scss']
|
||||
})
|
||||
export class MembershipCancelConfirmComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
public confirmDialogRef: MatDialogRef<MembershipCancelConfirmComponent>,
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
onCancel(): void {
|
||||
this.confirmDialogRef.close(false);
|
||||
}
|
||||
|
||||
onConfirm(): void {
|
||||
this.confirmDialogRef.close(true);
|
||||
}
|
||||
}
|
|
@ -5,11 +5,13 @@
|
|||
<p *ngFor="let paragraph of membershipDescription" class="mat-body">
|
||||
{{paragraph}}
|
||||
</p>
|
||||
<account-membership-options
|
||||
[membershipTypes]="membershipTypes"
|
||||
(membershipChange)="updateNewAccountForm($event)"
|
||||
<button
|
||||
mat-button
|
||||
type="button"
|
||||
*ngFor="let membershipType of membershipTypes" (click)="openPaymentDialog(membershipType)"
|
||||
>
|
||||
</account-membership-options>
|
||||
{{membershipType.rate + '/' + membershipType.ratePeriod.toUpperCase() + ' (USD)'}}
|
||||
</button>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
|
|
|
@ -32,8 +32,10 @@ mat-card {
|
|||
color: mat.get-color-from-palette(theme.$mycroft-primary, 500);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
mat-button-toggle-group {
|
||||
@include buttons.options-button-group
|
||||
button {
|
||||
@include buttons.action-button-primary;
|
||||
margin-bottom: 32px;
|
||||
margin-right: 16px;
|
||||
margin-top: 32px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ import { Component, Input, OnInit } from '@angular/core';
|
|||
import { MembershipType } from '@account/models/membership.model';
|
||||
import { MembershipUpdate } from '@account/models/membership-update.model';
|
||||
import { UntypedFormGroup } from '@angular/forms';
|
||||
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
|
||||
import { PaymentComponent } from '@account/app/modules/profile/components/views/payment/payment.component';
|
||||
|
||||
@Component({
|
||||
selector: 'account-membership-step',
|
||||
|
@ -31,7 +33,7 @@ export class MembershipStepComponent implements OnInit {
|
|||
@Input() newAcctForm: UntypedFormGroup;
|
||||
public membershipDescription: string[];
|
||||
|
||||
constructor() {
|
||||
constructor(public paymentDialog: MatDialog) {
|
||||
this.membershipDescription = [
|
||||
'Mycroft\'s voice assistant software is open source, which means it is free to use and ' +
|
||||
'the underlying source code is available to the public. Our entire platform is free ' +
|
||||
|
@ -49,11 +51,31 @@ export class MembershipStepComponent implements OnInit {
|
|||
ngOnInit() {
|
||||
}
|
||||
|
||||
openPaymentDialog(membershipType: MembershipType) {
|
||||
const dialogConfig = new MatDialogConfig();
|
||||
dialogConfig.data = {newAccount: false, membershipType: membershipType};
|
||||
dialogConfig.disableClose = true;
|
||||
dialogConfig.maxWidth = 520;
|
||||
dialogConfig.restoreFocus = true;
|
||||
const dialogRef = this.paymentDialog.open(PaymentComponent, dialogConfig);
|
||||
dialogRef.afterClosed().subscribe(
|
||||
(stripeToken) => {
|
||||
const membershipUpdate: MembershipUpdate = {
|
||||
action: 'add',
|
||||
membershipType: membershipType.type,
|
||||
paymentMethod: 'Stripe',
|
||||
paymentToken: stripeToken
|
||||
};
|
||||
this.updateNewAccountForm(membershipUpdate);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
updateNewAccountForm(membershipUpdate: MembershipUpdate): void {
|
||||
this.newAcctForm.patchValue(
|
||||
{
|
||||
membership: {
|
||||
newMembership: membershipUpdate.newMembership,
|
||||
action: membershipUpdate.action,
|
||||
membershipType: membershipUpdate.membershipType,
|
||||
paymentMethod: membershipUpdate.paymentMethod,
|
||||
paymentToken: membershipUpdate.paymentToken
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
<mat-card class="mat-elevation-z0">
|
||||
<mat-card-title>Thank you for supporting Mycroft!</mat-card-title>
|
||||
<mat-card-content>
|
||||
Your membership directly helps to improve Mycroft technology and user experience.
|
||||
<p class="mat-body">Please enter your payment information below</p>
|
||||
<mat-card class="mat-elevation-z0">
|
||||
<ngx-stripe-card [options]="cardOptions"></ngx-stripe-card>
|
||||
</mat-card>
|
||||
</mat-card-content>
|
||||
<mat-card-actions [align]="'end'">
|
||||
<button mat-button (click)="onCancel()">CANCEL</button>
|
||||
<button mat-button (click)="submitPaymentInfo()" class="submit-button">SUBMIT</button>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
<h1 mat-dialog-title>Thank you for supporting Mycroft!</h1>
|
||||
<mat-dialog-content>
|
||||
<p class="mat-body">
|
||||
Your membership directly helps to improve Mycroft's technology and user experience.
|
||||
</p>
|
||||
<p class="mat-body">
|
||||
Please enter your payment information below. Your credit card information is
|
||||
sent to the payment service and not stored on Mycroft's servers.
|
||||
</p>
|
||||
<div class="stripe-card">
|
||||
<ngx-stripe-card [options]="cardOptions"></ngx-stripe-card>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
<mat-dialog-actions>
|
||||
<button mat-button (click)="submitPaymentInfo()" class="submit-button">SUBMIT</button>
|
||||
<button mat-button (click)="onCancel()">CANCEL</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
|
@ -20,43 +20,22 @@
|
|||
@use "components/buttons" as buttons;
|
||||
@use "mycroft-theme" as theme;
|
||||
|
||||
mat-card {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
max-width: 500px;
|
||||
padding: 0;
|
||||
|
||||
mat-card-title {
|
||||
color: mat.get-color-from-palette(theme.$mycroft-primary, 700);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
mat-card-content {
|
||||
p {
|
||||
margin-top: 32px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
mat-card {
|
||||
border-radius: 10px;
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-color: mat.get-color-from-palette(theme.$mycroft-accent, 200);
|
||||
padding: 16px;
|
||||
margin-bottom: 40px;
|
||||
width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
.mat-card-actions.mat-card-actions {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.submit-button {
|
||||
@include buttons.action-button-primary;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: mat.get-color-from-palette(theme.$mycroft-primary, 700);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stripe-card {
|
||||
border-style: solid;
|
||||
border-color: #6c7a89;
|
||||
border-radius: 8px;
|
||||
border-width: thin;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 8px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.submit-button {
|
||||
@include buttons.action-button-primary;
|
||||
}
|
||||
|
|
|
@ -18,12 +18,13 @@ and limitations under the License.
|
|||
|
||||
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSnackBar, MatSnackBarConfig } from '@angular/material/snack-bar';
|
||||
|
||||
import { StripeCardComponent, StripeService } from 'ngx-stripe';
|
||||
import { StripeCardElementOptions} from '@stripe/stripe-js';
|
||||
|
||||
import { ProfileService } from '@account/http/profile.service';
|
||||
import { SnackbarComponent } from 'shared';
|
||||
|
||||
const twoSeconds = 2000;
|
||||
|
||||
|
@ -36,13 +37,14 @@ const twoSeconds = 2000;
|
|||
export class PaymentComponent implements OnInit {
|
||||
@ViewChild(StripeCardComponent) card: StripeCardComponent;
|
||||
public cardOptions: StripeCardElementOptions = {
|
||||
iconStyle: 'solid',
|
||||
style: {
|
||||
base: {
|
||||
iconColor: '#22a7f0',
|
||||
color: '#2c3e50',
|
||||
'::placeholder': {
|
||||
color: '#969fa8'
|
||||
}
|
||||
color: '#6c7a89'
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -61,26 +63,21 @@ export class PaymentComponent implements OnInit {
|
|||
}
|
||||
|
||||
submitPaymentInfo() {
|
||||
this.stripeService.createToken(this.card.element, {}).subscribe(
|
||||
result => {
|
||||
this.stripeService.createToken(this.card.element, {}).subscribe({
|
||||
next: (result) => {
|
||||
if (result.token) {
|
||||
this.dialogRef.close(result.token.id);
|
||||
} else if (result.error) {
|
||||
this.showStripeError(result.error.message);
|
||||
}
|
||||
},
|
||||
(result) => { this.showStripeError(result.toString()); }
|
||||
);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showStripeError(errorMessage: string) {
|
||||
this.dialogRef.close();
|
||||
this.snackbar.open(
|
||||
errorMessage,
|
||||
null,
|
||||
{panelClass: 'mycroft-no-action-snackbar', duration: twoSeconds}
|
||||
);
|
||||
const config = new MatSnackBarConfig();
|
||||
config.data = {type: 'error', message: errorMessage};
|
||||
this.snackbar.openFromComponent(SnackbarComponent, config);
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<account-membership-edit
|
||||
[accountMembership]="account.membership"
|
||||
[membershipTypes]="membershipTypes"
|
||||
(membershipUpdated)="refreshAccount()"
|
||||
>
|
||||
</account-membership-edit>
|
||||
<account-agreements-edit [account]="account"></account-agreements-edit>
|
||||
|
|
|
@ -46,4 +46,10 @@ export class EditComponent implements OnInit {
|
|||
updateOpenDataset(optIn: boolean) {
|
||||
this.service.updateAccount({openDataset: optIn}).subscribe();
|
||||
}
|
||||
|
||||
refreshAccount() {
|
||||
this.service.getAccount().subscribe({
|
||||
next: (account) => { this.account = account; }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ export class NewComponent implements OnInit {
|
|||
private buildForm() {
|
||||
const membershipGroup = this.formBuilder.group(
|
||||
{
|
||||
newMembership: [null],
|
||||
action: [null],
|
||||
membershipType: [null],
|
||||
paymentMethod: [null],
|
||||
paymentToken: [null]
|
||||
|
@ -140,9 +140,6 @@ export class NewComponent implements OnInit {
|
|||
|
||||
onFormSubmit() {
|
||||
const newValues = this.newAcctForm.value;
|
||||
if (!newValues.membership.newMembership) {
|
||||
delete newValues.membership;
|
||||
}
|
||||
this.profileService.updateAccount(newValues).subscribe(
|
||||
() => { this.router.navigate(['/']); }
|
||||
);
|
||||
|
|
|
@ -44,7 +44,7 @@ import { EditComponent } from './pages/edit/edit.component';
|
|||
import { environment} from '../../../environments/environment';
|
||||
import { LoginComponent } from './components/cards/login/login.component';
|
||||
import { MembershipComponent } from './components/cards/membership/membership.component';
|
||||
import { MembershipOptionsComponent } from './components/controls/membership-options/membership-options.component';
|
||||
import { MembershipCancelConfirmComponent } from './components/modals/membership-cancel-confirm/membership-cancel-confirm.component';
|
||||
import { MembershipStepComponent } from './components/views/membership-step/membership-step.component';
|
||||
import { NewComponent } from './pages/new/new.component';
|
||||
import { PaymentComponent } from './components/views/payment/payment.component';
|
||||
|
@ -66,12 +66,12 @@ import { VerifyEmailComponent } from './pages/verify-email/verify-email.componen
|
|||
EditComponent,
|
||||
LoginComponent,
|
||||
MembershipComponent,
|
||||
MembershipCancelConfirmComponent,
|
||||
// Profile add (i.e. new account)
|
||||
MembershipStepComponent,
|
||||
NewComponent,
|
||||
UsernameStepComponent,
|
||||
// Stuff used in both edit and add components
|
||||
MembershipOptionsComponent,
|
||||
PaymentComponent,
|
||||
VerifyCardDialogComponent,
|
||||
VerifyEmailComponent
|
||||
|
|
|
@ -18,7 +18,7 @@ and limitations under the License.
|
|||
|
||||
import { City } from '@account/models/city.model';
|
||||
import { Country } from '@account/models/country.model';
|
||||
import { PantacorConfig } from '@account/models/pantacorConfig.model';
|
||||
import { PantacorConfig } from '@account/models/pantacor-config.model';
|
||||
import { Region } from '@account/models/region.model';
|
||||
import { Timezone } from '@account/models/timezone.model';
|
||||
import { WakeWord } from '@account/models/wake-word.model';
|
||||
|
@ -40,5 +40,4 @@ export interface Device {
|
|||
voice: Voice;
|
||||
wakeWord: WakeWord;
|
||||
pantacorConfig: PantacorConfig;
|
||||
pantacorUpdateId: string;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ and limitations under the License.
|
|||
***************************************************************************** */
|
||||
|
||||
export interface MembershipUpdate {
|
||||
action: string;
|
||||
membershipType?: string;
|
||||
newMembership: boolean;
|
||||
paymentMethod?: string;
|
||||
paymentToken?: string;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ export interface PantacorConfig {
|
|||
autoUpdate: boolean;
|
||||
ipAddress: string;
|
||||
pantacorId: string;
|
||||
deploymentId: string;
|
||||
sshPublicKey: string;
|
||||
releaseChannel: string;
|
||||
}
|
|
@ -140,3 +140,4 @@ $mycroft-theme: mat.define-light-theme((
|
|||
@include mat.stepper-theme($mycroft-theme);
|
||||
@include mat.tabs-theme($mycroft-theme);
|
||||
@include mat.toolbar-theme($mycroft-theme);
|
||||
@include mat.tooltip-theme($mycroft-theme);
|
||||
|
|
Loading…
Reference in New Issue