Skip to content

Commit

Permalink
New user journey dev (#2766)
Browse files Browse the repository at this point in the history
* New payment details implementation

* Fix tests

* Antifraud for v1 plans

* Adjust tests

* fix for lack of first_sync for assembla

* added hosted billing help

* Add the new macOS information section in select-plan and modify billing scss styles

* Adjust manual and github plans view

* Fix local registration checkbox

* ui updates

* redirection attempt, wizard fix, trial plan desc

* ui updates

* sync subscriptions on first_syn

* redirect from ghapp installation to firstsync

* style fixes,profile menu update,fix for company

* tests update

* ui updates

* ui updates

* redirection updates, typos fixed

* ui updates - plan, wizard, activation

* coupon

* installation redirections, email banner update

* random ui updates

* lint

* total price hide

* empty invoices field, installation_id fix

* radio color,mail banner, double badge

* ui fixes - review 03.28

* sync popup improvement

* activation button fix

* lint
  • Loading branch information
GbArc authored Jul 24, 2023
1 parent d9d5bc4 commit e3a7015
Show file tree
Hide file tree
Showing 98 changed files with 5,521 additions and 893 deletions.
2 changes: 1 addition & 1 deletion app/adapters/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default V3Adapter.extend({

assert('Invalid parameters for /user request', isQueryingCurrentUser || isUpdatingCurrentUser);

return `${this.urlPrefix()}/user`;
return `${this.urlPrefix()}/user?include=user.collaborator`;
},

// This overrides the parent implementation to ignore the query parameters
Expand Down
302 changes: 302 additions & 0 deletions app/components/billing/first-plan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { inject as service } from '@ember/service';
import { not, reads, filterBy, alias } from '@ember/object/computed';
import { computed } from '@ember/object';
import config from 'travis/config/environment';
import { countries, states, zeroVatThresholdCountries, nonZeroVatThresholdCountries, stateCountries } from 'travis/utils/countries';

export default Component.extend({
stripe: service(),
store: service(),
auth: service(),
accounts: service(),
flashes: service(),
metrics: service(),
storage: service(),
router: service(),
wizard: service('wizard-state'),
countries,
user: null,
account: alias('accounts.user'),
stripeElement: null,
stripeLoading: false,
couponId: null,
options: config.stripeOptions,
showSwitchToFreeModal: false,
showPlanSwitchWarning: false,
availablePlans: reads('account.eligibleV2Plans'),
defaultPlans: filterBy('availablePlans', 'trialPlan'),
defaultPlanId: reads('defaultPlans.firstObject.id'),
showCancelButton: false,
travisTermsUrl: 'https://www.ideracorp.com/Legal/PrivacyShield',
travisPolicyUrl: 'https://www.ideracorp.com/Legal/PrivacyShield',
subscription: null,
vatId: null,

displayedPlans: reads('availablePlans'),

selectedPlan: computed('displayedPlans.[].id', 'defaultPlanId', function () {
let plan = this.storage.selectedPlanId;
if (plan == null) {
plan = this.defaultPlanId;
}

return this.displayedPlans.findBy('id', plan);
}),

isTrial: computed('selectedPlan', function () {
let plan = this.selectedPlan;
return plan ? plan.isTrial : true;
}),

hasLocalRegistration: false,
firstName: '',
lastName: '',
company: '',
address: '',
city: '',
country: '',
billingEmail: '',
billingEmails: computed('billingEmail', function () {
return (this.billingEmail || '').split(',');
}),

states: computed('country', function () {
const { country } = this;

return states[country];
}),

isStateCountry: computed('country', function () {
const { country } = this;

return !!country && stateCountries.includes(country);
}),

isZeroVatThresholdCountry: computed('country', function () {
const { country } = this;
return !!country && zeroVatThresholdCountries.includes(country);
}),

isNonZeroVatThresholdCountry: computed('country', function () {
const { country } = this;
return !!country && nonZeroVatThresholdCountries.includes(country);
}),

isVatMandatory: computed('isNonZeroVatThresholdCountry', 'hasLocalRegistration', function () {
const { isNonZeroVatThresholdCountry, isZeroVatThresholdCountry, hasLocalRegistration } = this;
return isZeroVatThresholdCountry || (isNonZeroVatThresholdCountry ? hasLocalRegistration : false);
}),

showNonZeroVatConfirmation: reads('isNonZeroVatThresholdCountry'),

showVatField: computed('country', 'isNonZeroVatThresholdCountry', 'hasLocalRegistration', function () {
const { country, isNonZeroVatThresholdCountry, hasLocalRegistration } = this;
return country && (isNonZeroVatThresholdCountry ? hasLocalRegistration : true);
}),

isStateMandatory: reads('isStateCountry'),

isLoading: false,
planDetailsVisible: false,

isNewSubscription: not('subscription.id'),

creditCardInfo: null,
creditCardOwner: null,

creditCardInfoEmpty: computed('subscription.creditCardInfo', function () {
return !this.creditCardInfo.lastDigits;
}),

getPriceInfo: computed('selectedPlan', function () {
let plan = this.selectedPlan;
return `$${plan.startingPrice} ${(plan.isAnnual ? ' annualy' : ' monthly')}`;
}),

getActivateButtonText: computed('selectedPlan', function () {
let text = 'Verify Your Account';
let plan = this.selectedPlan;
if (plan && !plan.isTrial) {
text = `Activate ${plan.name}`;
}
return text;
}),

canActivate: computed('country', 'zipCode', 'address', 'creditCardOwner', 'city', 'stripeElement', 'billingEmail', function () {
let valid = (val) => !(val === null || val.trim() === '');
return valid(this.billingEmail) && valid(this.country) &&
valid(this.zipCode) && valid(this.address) &&
valid(this.creditCardOwner) && this.stripeElement &&
valid(this.city);
}),

createSubscription: task(function* () {
this.metrics.trackEvent({
action: 'Pay Button Clicked',
category: 'Subscription',
});
const { stripeElement, selectedPlan } = this;
try {
this.set('subscription', this.newV2Subscription());
const { token } = yield this.stripe.createStripeToken.perform(stripeElement);
if (token) {
const organizationId = null;
const plan = selectedPlan && selectedPlan.id && this.store.peekRecord('v2-plan-config', selectedPlan.id);
const org = organizationId && this.store.peekRecord('organization', organizationId);

this.subscription.setProperties({
organization: org,
plan: plan,
v1SubscriptionId: this.v1SubscriptionId,
});
if (!this.subscription.id) {
this.subscription.creditCardInfo.setProperties({
token: token.id,
lastDigits: token.card.last4
});
this.subscription.setProperties({
coupon: this.couponId
});
const { clientSecret } = yield this.subscription.save();
yield this.stripe.handleStripePayment.perform(clientSecret);
} else {
yield this.subscription.creditCardInfo.updateToken.perform({
subscriptionId: this.subscription.id,
tokenId: token.id,
tokenCard: token.card
});
yield this.subscription.save();
yield this.subscription.changePlan.perform(selectedPlan.id, this.couponId);
yield this.accounts.fetchV2Subscriptions.perform();
yield this.retryAuthorization.perform();
}
this.metrics.trackEvent({ button: 'pay-button' });
this.storage.clearBillingData();
this.storage.clearSelectedPlanId();
this.storage.wizardStep = 2;
this.wizard.update.perform(2);
yield this.accounts.fetchV2Subscriptions.perform().then(() => {
this.router.transitionTo('/account/repositories');
});
}
this.flashes.success('Your account has been successfully activated');
} catch (error) {
yield this.accounts.fetchV2Subscriptions.perform().then(() => {
if (this.accounts.user.subscription || this.accounts.user.v2subscription) {
this.storage.clearBillingData();
this.storage.clearSelectedPlanId();
this.storage.wizardStep = 2;
this.wizard.update.perform(2);
this.router.transitionTo('account.repositories');
} else {
this.handleError();
}
});
}
}).drop(),

newV2Subscription() {
const plan = this.store.createRecord('v2-plan-config');
const billingInfo = this.store.createRecord('v2-billing-info');
const creditCardInfo = this.store.createRecord('v2-credit-card-info');
let ownerName = this.creditCardOwner.trim();
let idx = ownerName.lastIndexOf(' ');
if (idx > 0) {
this.firstName = ownerName.substr(0, idx);
this.lastName = ownerName.substr(idx + 1);
} else {
this.firstName = '';
this.lastName = ownerName;
}
let empty = (val) => val === null || val.trim() === '';
if (empty(this.lastName) || empty(this.address) ||
empty(this.city) || empty(this.zipCode) ||
empty(this.country) || empty(this.billingEmail)
) {
throw new Error('Fill all required fields');
}
billingInfo.setProperties({
firstName: this.firstName,
lastName: this.lastName,
address: this.address,
city: this.city,
company: this.company,
zipCode: this.zipCode,
country: this.country,
state: this.state,
billingEmail: this.billingEmail,
hasLocalRegistration: this.hasLocalRegistration,
vatId: this.vatId
});
creditCardInfo.setProperties({
token: '',
lastDigits: ''
});
return this.store.createRecord('v2-subscription', {
billingInfo,
plan,
creditCardInfo,
});
},
handleError() {
let message = this.get('selectedPlan.isTrial')
? 'Credit card verification failed, please try again or use a different card.'
: 'An error occurred when creating your subscription. Please try again.';
this.flashes.error(message);
},

validateCoupon: task(function* () {
return yield this.store.findRecord('coupon', this.couponId, {
reload: true,
});
}).drop(),

coupon: reads('validateCoupon.last.value'),
couponError: reads('validateCoupon.last.error'),
isValidCoupon: reads('coupon.valid'),
couponHasError: computed('couponError', {
get() {
return !!this.couponError;
},
set(key, value) {
return value;
}
}),

actions: {
complete(stripeElement) {
this.set('stripeElement', stripeElement);
},
handleCouponFocus() {
this.set('couponHasError', false);
},

clearCreditCardData() {
this.subscription.set('creditCardInfo', null);
},
changePlan() {
this.set('showPlansSelector', true);
},
closePlansModal() {
this.set('showPlansSelector', false);
},
verifyAccount() {

},
subscribe() {
if (this.canActivate) {
this.createSubscription.perform();
}
},
changeCountry(country) {
this.set('country', country);
this.hasLocalRegistration = false;
},
togglePlanDetails() {
this.set('planDetailsVisible', !this.planDetailsVisible);
}

}
});
Loading

0 comments on commit e3a7015

Please sign in to comment.