fixed several outstanding issues with the membership selection

pull/4/head
Chris Veilleux 2019-04-19 18:51:25 -05:00
parent 953c1caeda
commit 037f9376c1
9 changed files with 116 additions and 152 deletions

View File

@ -7,7 +7,7 @@
<account-membership-options
[accountMembership]="accountMembership"
[membershipTypes]="membershipTypes"
(membershipChange)="onMembershipChange($event)"
(membershipChange)="updateAccount($event)"
>
</account-membership-options>
<span *ngIf="accountMembership && accountMembership.duration" id="subscription-date" class="mat-body">

View File

@ -1,12 +1,15 @@
import { Component, Input, OnDestroy } from '@angular/core';
import { MediaChange, MediaObserver } from '@angular/flex-layout';
import { MatBottomSheet } from '@angular/material';
import { MatSnackBar } from '@angular/material';
import { Subscription } from 'rxjs';
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';
import { PaymentComponent } from '../../views/payment/payment.component';
const twoSeconds = 2000;
@Component({
selector: 'account-membership-edit',
@ -15,14 +18,14 @@ import { PaymentComponent } from '../../views/payment/payment.component';
})
export class MembershipComponent implements OnDestroy {
@Input() accountMembership: AccountMembership;
public alignVertical: boolean;
@Input() membershipTypes: MembershipType[];
public alignVertical: boolean;
private mediaWatcher: Subscription;
constructor(
public bottomSheet: MatBottomSheet,
public mediaObserver: MediaObserver,
private profileService: ProfileService,
private snackbar: MatSnackBar
) {
this.mediaWatcher = mediaObserver.media$.subscribe(
(change: MediaChange) => {
@ -35,43 +38,15 @@ export class MembershipComponent implements OnDestroy {
this.mediaWatcher.unsubscribe();
}
onMembershipChange(membershipType: string) {
const selectedMembership = this.membershipTypes.find(
(membership) => membership.type === membershipType
);
if (selectedMembership) {
if (this.accountMembership) {
// We have the user's credit card info but they decide to change plans
this.profileService.updateAccount(
{membership: {paymentMethod: 'Stripe', newMembership: false, membershipType: membershipType}}
);
} else {
// No credit card info. Go to payment screen to collect
this.openBottomSheet(membershipType);
}
} else {
// Membership termination
this.profileService.updateAccount(
{membership: {paymentMethod: 'Stripe', newMembership: false, membershipType: null}}
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}
);
}
}
openBottomSheet(membershipType: string) {
const bottomSheetConfig = {
data: {newAccount: false, membershipType: membershipType},
disableClose: true,
restoreFocus: true
};
const bottomSheetRef = this.bottomSheet.open(PaymentComponent, bottomSheetConfig);
bottomSheetRef.afterDismissed().subscribe(
(dismissValue) => {
if (dismissValue === 'cancel') {
this.profileService.setSelectedMembershipType(
this.accountMembership,
this.membershipTypes
);
}
}
);
}

View File

@ -1,12 +1,14 @@
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
import { MediaChange, MediaObserver } from '@angular/flex-layout';
import { MatButtonToggleChange } from '@angular/material';
import { MatButtonToggleChange, MatDialog, MatDialogConfig, MatSnackBar } from '@angular/material';
import { Subscription } from 'rxjs';
import { AccountMembership } from '../../../../../shared/models/account-membership.model';
import { MembershipType } from '../../../../../shared/models/membership.model';
import { ProfileService } from '../../../../../core/http/profile.service';
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({
@ -16,13 +18,18 @@ import { ProfileService } from '../../../../../core/http/profile.service';
})
export class MembershipOptionsComponent implements OnInit, OnDestroy {
@Input() accountMembership: AccountMembership;
public alignVertical: boolean;
@Input() membershipTypes: MembershipType[];
@Output() membershipChange = new EventEmitter<MembershipUpdate>();
public alignVertical: boolean;
public mediaWatcher: Subscription;
@Output() membershipChange = new EventEmitter<string>();
public selectedMembershipType: string;
constructor(public mediaObserver: MediaObserver, private profileService: ProfileService) {
constructor(
public mediaObserver: MediaObserver,
private profileService: ProfileService,
public paymentDialog: MatDialog,
private snackbar: MatSnackBar
) {
this.mediaWatcher = mediaObserver.media$.subscribe(
(change: MediaChange) => {
this.alignVertical = ['xs', 'sm'].includes(change.mqAlias);
@ -45,9 +52,49 @@ export class MembershipOptionsComponent implements OnInit, OnDestroy {
this.mediaWatcher.unsubscribe();
}
onMembershipSelect(newMembershipType: MatButtonToggleChange) {
this.profileService.selectedMembershipType.next(newMembershipType.value);
this.membershipChange.emit(newMembershipType.value);
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);
}
}
);
}
}

View File

@ -8,7 +8,7 @@
</mat-card>
</mat-card-content>
<mat-card-actions align="right">
<button mat-button id="cancel-button" (click)="onCancel()">CANCEL</button>
<button mat-button id="submit-button" (click)="submitPayment()">SUBMIT</button>
<button mat-button (click)="onCancel()">CANCEL</button>
<button mat-button (click)="submitPaymentInfo()" class="submit-button">SUBMIT</button>
</mat-card-actions>
</mat-card>

View File

@ -1,13 +1,12 @@
@import "../../../../../../../../../node_modules/@angular/material/theming";
@import "~@angular/material/theming";
@import "mycroft-colors";
@import "../../../../../../../../../src/stylesheets/components/buttons";
@import "components/buttons";
mat-card {
margin-right: auto;
margin-left: auto;
margin-bottom: 16px;
max-width: 500px;
padding: 32px;
padding: 0;
mat-card-title {
color: mat-color($mycroft-primary, 700);
@ -28,12 +27,18 @@ mat-card {
border-width: 1px;
border-color: mat-color($mycroft-accent, 200);
padding: 16px;
margin-bottom: 40px;
width: 320px;
}
}
#submit-button {
@include action-button-primary;
margin-top: 16px
.mat-card-actions.mat-card-actions {
padding: 0;
margin: 0;
.submit-button {
@include action-button-primary;
}
}
}

View File

@ -1,7 +1,6 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Component, Inject, OnInit, ViewChild } from '@angular/core';
import {
MatBottomSheetRef,
MatDialog,
MAT_DIALOG_DATA,
MatDialogRef,
MatSnackBar
} from '@angular/material';
@ -9,7 +8,6 @@ import {
import { ElementOptions, StripeCardComponent, StripeService } from 'ngx-stripe';
import { ProfileService } from '@account/http/profile.service';
import { VerifyCardDialogComponent } from './verify-card-dialog.component';
const twoSeconds = 2000;
@ -32,14 +30,13 @@ export class PaymentComponent implements OnInit {
}
}
};
private dialogRef: MatDialogRef<VerifyCardDialogComponent>;
constructor(
private bottomSheetRef: MatBottomSheetRef<PaymentComponent>,
private paymentSnackbar: MatSnackBar,
public dialogRef: MatDialogRef<PaymentComponent>,
private snackbar: MatSnackBar,
private profileService: ProfileService,
private stripeService: StripeService,
public verifyCardDialog: MatDialog
@Inject(MAT_DIALOG_DATA) public dialogData: any
) {
}
@ -47,60 +44,23 @@ export class PaymentComponent implements OnInit {
ngOnInit() {
}
submitPayment() {
this.openDialog();
submitPaymentInfo() {
this.stripeService.createToken(this.card.getCard(), {}).subscribe(
result => {
if (result.token) {
const configData = this.bottomSheetRef.containerInstance.bottomSheetConfig.data;
if (configData.newAccount) {
this.showStripeSuccess(result.token.id);
} else {
this.updateAccount(configData.membershipType, result.token.id);
}
this.dialogRef.close(result.token.id);
} else if (result.error) {
this.showStripeError(result.error.message);
}
}
},
(result) => { this.showStripeError(result.toString()); }
);
}
openDialog(): void {
this.dialogRef = this.verifyCardDialog.open(
VerifyCardDialogComponent,
{width: '250px'}
);
}
updateAccount(membershipType: string, stripeToken: string) {
const newMembership = {
membership: {
newMembership: true,
membershipType: membershipType,
paymentMethod: 'Stripe',
paymentToken: stripeToken
}
};
this.profileService.updateAccount(newMembership).subscribe(
() => { this.showStripeSuccess(stripeToken); }
);
}
showStripeSuccess(stripeToken: string) {
this.dialogRef.close();
const paymentSnackbarRef = this.paymentSnackbar.open(
'Card verification successful',
null,
{panelClass: 'mycroft-no-action-snackbar', duration: twoSeconds}
);
paymentSnackbarRef.afterDismissed().subscribe(
() => { this.bottomSheetRef.dismiss(stripeToken); }
);
}
showStripeError(errorMessage: string) {
this.dialogRef.close();
this.paymentSnackbar.open(
this.snackbar.open(
errorMessage,
null,
{panelClass: 'mycroft-no-action-snackbar', duration: twoSeconds}
@ -108,6 +68,6 @@ export class PaymentComponent implements OnInit {
}
onCancel() {
this.bottomSheetRef.dismiss('cancel');
this.dialogRef.close();
}
}

View File

@ -5,7 +5,7 @@
</p>
<account-membership-options
[membershipTypes]="membershipTypes"
(membershipChange)="onMembershipSelection($event)"
(membershipChange)="updateNewAccountForm($event)"
>
</account-membership-options>
</mat-card>

View File

@ -2,8 +2,9 @@ import { Component, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { MatBottomSheet } from '@angular/material';
import { MembershipType } from '../../../../../shared/models/membership.model';
import { ProfileService } from '../../../../../core/http/profile.service';
import { MembershipType } from '@account/models/membership.model';
import { MembershipUpdate } from '@account/models/membership-update.model';
import { ProfileService } from '@account/http/profile.service';
import { PaymentComponent } from '../payment/payment.component';
@Component({
@ -17,9 +18,9 @@ export class SupportStepComponent implements OnInit {
public openDatasetDescription: string[];
public membershipDescription: string[];
constructor(public bottomSheet: MatBottomSheet, private profileService: ProfileService) { }
constructor() { }
ngOnInit() {
ngOnInit(): void {
this.openDatasetDescription = [
'Mycroft\'s voices and services can only improve with your help. ' +
'By joining our open dataset, you agree to allow Mycroft AI to collect data related ' +
@ -48,51 +49,21 @@ export class SupportStepComponent implements OnInit {
];
}
onOptIn() {
onOptIn(): void {
this.newAcctForm.patchValue({support: {openDataset: true}});
}
onOptOut() {
onOptOut(): void {
this.newAcctForm.patchValue({support: {openDataset: false}});
}
onMembershipSelection(membershipType: string) {
const selectedMembership = this.membershipTypes.find(
(membership) => membership.type === membershipType
);
if (selectedMembership) {
this.openBottomSheet(selectedMembership.type);
} else {
this.newAcctForm.patchValue({support: {membership: null}});
}
}
openBottomSheet(selectedMembership: string) {
const bottomSheetConfig = {
data: {newAccount: true},
disableClose: true,
restoreFocus: true
};
const bottomSheetRef = this.bottomSheet.open(PaymentComponent, bottomSheetConfig);
bottomSheetRef.afterDismissed().subscribe(
(dismissValue) => {
if (dismissValue === 'cancel') {
this.profileService.selectedMembershipType.next('Maybe Later');
} else {
this.updateNewAccountForm(selectedMembership, dismissValue);
}
}
);
}
updateNewAccountForm(selectedMembership: string, stripeToken: string) {
console.log(stripeToken);
updateNewAccountForm(membershipUpdate: MembershipUpdate): void {
this.newAcctForm.patchValue(
{
support: {
membership: selectedMembership,
paymentMethod: 'Stripe',
paymentToken: stripeToken
membership: membershipUpdate.membershipType,
paymentMethod: membershipUpdate.paymentMethod,
paymentToken: membershipUpdate.paymentToken
}
}
);

View File

@ -0,0 +1,6 @@
export interface MembershipUpdate {
membershipType?: string;
newMembership: boolean;
paymentMethod?: string;
paymentToken?: string;
}