Skip to content

Commit

Permalink
Make the onboarding flow more flexible
Browse files Browse the repository at this point in the history
  • Loading branch information
joshsmith committed Mar 27, 2017
1 parent a981f27 commit 9fc7956
Show file tree
Hide file tree
Showing 42 changed files with 434 additions and 280 deletions.
7 changes: 5 additions & 2 deletions app/components/thank-you-container.js
@@ -1,9 +1,12 @@
import Ember from 'ember';

const {
Component
Component,
inject: { service }
} = Ember;

export default Component.extend({
classNames: ['thank-you-container']
classNames: ['thank-you-container'],

onboarding: service()
});
3 changes: 2 additions & 1 deletion app/controllers/application.js
Expand Up @@ -3,6 +3,7 @@ import Ember from 'ember';
const {
computed,
Controller,
get,
inject: { service }
} = Ember;

Expand All @@ -22,7 +23,7 @@ export default Controller.extend({

actions: {
invalidateSession() {
this.get('session').invalidate();
get(this, 'session').invalidate();
}
}
});
41 changes: 32 additions & 9 deletions app/mixins/onboarding-controller.js
Expand Up @@ -2,8 +2,11 @@ import Ember from 'ember';

const {
computed,
get,
getProperties,
inject: { service },
Mixin
Mixin,
set
} = Ember;

export default Mixin.create({
Expand All @@ -14,14 +17,34 @@ export default Mixin.create({

actions: {
continue() {
let user = this.get('user');
let onboarding = this.get('onboarding');
let nextStateTransition = onboarding.get('nextStateTransition');
let nextRoute = onboarding.get('nextRoute');
user.set('stateTransition', nextStateTransition);
user.save().then(() => {
this.transitionToRoute(nextRoute);
});
let { onboarding, user } = getProperties(this, 'onboarding', 'user');
let { nextStateTransition, shouldTransitionUser } = getProperties(onboarding, 'nextStateTransition', 'shouldTransitionUser');

// We can transition routes without transitioning the user's state
if (shouldTransitionUser) {
set(user, 'stateTransition', nextStateTransition);
}

this._transitionToNextRoute();
},

skip() {
let { onboarding, user } = getProperties(this, 'onboarding', 'user');
let { skipStateTransition, shouldTransitionUser } = getProperties(onboarding, 'skipStateTransition', 'shouldTransitionUser');

// We can transition routes without transitioning the user's state
if (shouldTransitionUser) {
set(user, 'stateTransition', skipStateTransition);
}

this._transitionToNextRoute();
}
},

_transitionToNextRoute() {
let { onboarding, user } = getProperties(this, 'onboarding', 'user');
user.save().then(() => {
this.transitionToRoute(get(onboarding, 'nextRoute'));
});
}
});
34 changes: 0 additions & 34 deletions app/mixins/onboarding-route.js

This file was deleted.

1 change: 1 addition & 0 deletions app/models/user.js
Expand Up @@ -16,6 +16,7 @@ export default Model.extend({
password: attr(),
photoLargeUrl: attr(),
photoThumbUrl: attr(),
signUpContext: attr(),
state: attr(),
twitter: attr(),
username: attr(),
Expand Down
23 changes: 10 additions & 13 deletions app/router.js
Expand Up @@ -29,10 +29,12 @@ let AppRouter = Router.extend({
});

AppRouter.map(function() {
this.route('about');

this.route('login');

this.route('oauth-stripe', {
path: '/oauth/stripe'
this.route('organization', function() {
this.route('settings', function() { });
});

this.route('organizations', function() {
Expand All @@ -45,6 +47,8 @@ AppRouter.map(function() {
});
});

this.route('privacy');

this.route('project', { path: '/:slugged_route_slug/:project_slug' }, function() {
this.route('checkout');
this.route('donate');
Expand Down Expand Up @@ -77,25 +81,18 @@ AppRouter.map(function() {

this.route('signup');

this.route('slugged-route', {
path: '/:slugged_route_slug'
});

this.route('start', function() {
this.route('hello');
this.route('interests');
this.route('expertise');
this.route('skills');
});

this.route('slugged-route', {
path: '/:slugged_route_slug'
});

this.route('team');
this.route('about');

this.route('organization', function() {
this.route('settings', function() { });
});

this.route('privacy');
this.route('terms');
});

Expand Down
75 changes: 41 additions & 34 deletions app/routes/application.js
Expand Up @@ -19,7 +19,7 @@ export default Route.extend(ApplicationRouteMixin, LoadingBar, {
onboarding: service(),

isOnboarding: computed.alias('onboarding.isOnboarding'),
onboardingRoute: computed.alias('onboarding.currentRoute'),
onboardingRoute: computed.alias('onboarding.routeForCurrentStep'),

headTags: [
{
Expand Down Expand Up @@ -153,60 +153,63 @@ export default Route.extend(ApplicationRouteMixin, LoadingBar, {
],

beforeModel(transition) {
return this._loadCurrentUser().then(() => {
if (this._shouldTransitionToOnboardingRoute(transition)) {
return this.transitionTo(get(this, 'onboardingRoute'));
} else {
return this._super(...arguments);
}
}).catch(() => this._invalidateSession());
return this._loadCurrentUser()
.then(() => {
if (this._shouldRedirectToOnboarding(transition)) {
return this.transitionTo(get(this, 'onboardingRoute'));
} else {
return this._super(...arguments);
}
})
.catch(() => this._invalidateSession());
},

sessionAuthenticated() {
return this._loadCurrentUser()
.then(() => {
this._attemptTransition();
this._attemptTransitionAfterAuthentication();
this._trackAuthentication();
})
.catch(() => this._invalidateSession());
},

actions: {
willTransition(transition) {
if (this._shouldTransitionToOnboardingRoute(transition)) {
this._abortAndFixHistory(transition);
if (this._shouldRedirectToOnboarding(transition)) {
transition.abort();
this.transitionTo(get(this, 'onboardingRoute'));
}
},

// see https://github.com/emberjs/ember.js/issues/12791
// if we don't handle the error action at application level
// te error will continue to be thrown, causing tests to fail
// and the error to be outputed to console, even though we technically
// the error will continue to be thrown, causing tests to fail
// and the error will output to console, even though we technically
// "handled" it with our application_error route/template
error(e) {
console.error(e);
this.intermediateTransitionTo('application_error', e);
}
},

_abortAndFixHistory(transition) {
transition.abort();
if (window.history) {
window.history.forward();
}
},

_attemptTransition() {
if (get(this, 'isOnboarding')) {
// The default beahavior for an ember-simple-auth ApplicationRouteMixin is to
// Either got back to the route the user tried to access before being
// redirected to login/signup, or if there is no such route, go to the default
// route.
//
// This slightly extends this behavior by sending the user to the onboarding
// route if it's determined that should be the case. If there was a stored
// route, for example, if a non-signed-in user tried to donate, then that
// still takes precedence.
_attemptTransitionAfterAuthentication() {
let attemptedTransition = get(this, 'session.attemptedTransition');
if (isPresent(attemptedTransition)) {
attemptedTransition.retry();
set(this, 'session.attemptedTransition', null);
} else if (get(this, 'isOnboarding')) {
this.transitionTo(get(this, 'onboardingRoute'));
} else {
let attemptedTransition = get(this, 'session.attemptedTransition');
if (isPresent(attemptedTransition)) {
attemptedTransition.retry();
set(this, 'session.attemptedTransition', null);
} else {
this.transitionTo('projects-list');
}
this.transitionTo('projects-list');
}
},

Expand All @@ -218,14 +221,18 @@ export default Route.extend(ApplicationRouteMixin, LoadingBar, {
return get(this, 'currentUser').loadCurrentUser();
},

_shouldTransitionToOnboardingRoute(transition) {
// If the user is still in the process of onboarding, they are allowd to visit
// only select routes. Trying to access any other route redirects them to
// their next onboarding route.
//
// This function returns true if that should happen.
_shouldRedirectToOnboarding(transition) {
let isOnboarding = get(this, 'isOnboarding');

let onboardingRoutes = get(this, 'onboarding.routes');
let allowedRoutes = get(this, 'onboarding.allowedRoutes');
let targetRoute = transition.targetName;
let isTransitionToOnboardingRoute = (onboardingRoutes.indexOf(targetRoute) > -1);
let isTransitionToAllowedRoute = (allowedRoutes.indexOf(targetRoute) > -1);

return isOnboarding && !isTransitionToOnboardingRoute;
return isOnboarding && !isTransitionToAllowedRoute;
},

_trackAuthentication() {
Expand Down
4 changes: 2 additions & 2 deletions app/routes/project/checkout.js
Expand Up @@ -21,7 +21,7 @@ export default Route.extend({
return this._super(...arguments);
} else {
set(session, 'attemptedTransition', transition);
let queryParams = { donate: true };
let queryParams = { context: 'donation' };
return this.transitionTo('signup', { queryParams });
}
},
Expand All @@ -38,7 +38,7 @@ export default Route.extend({
get(this, 'flashMessages').success(ALREADY_A_SUBSCRIBER);
this.transitionTo('project', project);
} else {
this._super.call(...arguments);
this._super(...arguments);
}
},

Expand Down
29 changes: 27 additions & 2 deletions app/routes/project/donate.js
@@ -1,12 +1,37 @@
import Ember from 'ember';

const {
Route
get,
inject: { service },
Route,
RSVP
} = Ember;

const ALREADY_A_SUBSCRIBER = "You're already supporting this project.";

export default Route.extend({
flashMessages: service(),
session: service(),
userSubscriptions: service(),

model() {
return this.modelFor('project').reload();
return this.modelFor('project').reload().then((project) => {
let subscription = get(this, 'userSubscriptions').fetchForProject(project);
return RSVP.hash({ project, subscription });
});
},

afterModel({ project, subscription }) {
if (subscription) {
get(this, 'flashMessages').success(ALREADY_A_SUBSCRIBER);
this.transitionTo('project', project);
} else {
this._super(...arguments);
}
},

setupController(controller, models) {
controller.setProperties(models);
},

renderTemplate() {
Expand Down

0 comments on commit 9fc7956

Please sign in to comment.