diff --git a/app/controllers/project/donate.js b/app/controllers/project/donate.js index 8f862a80e..dd97726a1 100644 --- a/app/controllers/project/donate.js +++ b/app/controllers/project/donate.js @@ -1,5 +1,6 @@ import Ember from 'ember'; import FriendlyError from 'code-corps-ember/utils/friendly-error'; +import { isValidationError } from 'code-corps-ember/utils/error-utils'; const { Controller, @@ -14,16 +15,6 @@ const CUSTOMER_CREATION_ERROR = 'There was a problem in connecting your account const CARD_CREATION_ERROR = 'There was a problem in using your payment information. Please try again.'; const SUBSCRIPTION_CREATION_ERROR = 'There was a problem in setting up your monthly donation. Please try again.'; -function isValidationError(payload) { - if (!payload.isAdapterError) { - return false; - } - - let errors = payload.errors || []; - - return errors.some((e) => e.id == 'VALIDATION_ERROR'); -} - export default Controller.extend({ amount: null, isAddingCard: false, diff --git a/app/controllers/project/settings/donations.js b/app/controllers/project/settings/donations.js index 4bcdf3afa..bd1ef9840 100644 --- a/app/controllers/project/settings/donations.js +++ b/app/controllers/project/settings/donations.js @@ -1,4 +1,6 @@ import Ember from 'ember'; +import FriendlyError from 'code-corps-ember/utils/friendly-error'; +import { isValidationError } from 'code-corps-ember/utils/error-utils'; const { Controller, @@ -7,6 +9,8 @@ const { set } = Ember; +const PROBLEM_SAVING_DONATION_GOAL = 'There was a problem saving your donation goal. Please try again.'; + export default Controller.extend({ projectDonationGoals: service(), store: service(), @@ -20,8 +24,8 @@ export default Controller.extend({ */ activateDonations(project) { get(this, 'store') - .createRecord('stripe-connect-plan', { project }) - .save(); + .createRecord('stripe-connect-plan', { project }) + .save(); }, /** @@ -74,7 +78,7 @@ export default Controller.extend({ /** * Action which commits changes to a donation goal. * - * Triggers when user clicks the save button while edditing or + * Triggers when user clicks the save button while editing or * adding a new donation goal. * * @method saveDonationGoal @@ -83,16 +87,19 @@ export default Controller.extend({ */ saveDonationGoal(donationGoal, properties) { donationGoal.setProperties(properties); - console.log('set'); - donationGoal.save().then((donationGoal) => { - let projectDonationGoals = get(this, 'projectDonationGoals'); - let project = get(this, 'project'); - project.reload().then((project) => { - projectDonationGoals.reload(project).then(() => { - this.send('cancelDonationGoal', donationGoal); - }); - }); - }); + donationGoal.save() + .then((donationGoal) => this._onDoneSaving(donationGoal)) + .catch((response) => this._onFailedSaving(response)); + } + }, + + _onDoneSaving(donationGoal) { + set(donationGoal, 'isEditing', false); + }, + + _onFailedSaving(response) { + if (!isValidationError(response)) { + set(this, 'error', new FriendlyError(PROBLEM_SAVING_DONATION_GOAL)); } } }); diff --git a/app/styles/components/donation-goal-edit.scss b/app/styles/components/donation-goal-edit.scss index ae2117417..25ee688c9 100644 --- a/app/styles/components/donation-goal-edit.scss +++ b/app/styles/components/donation-goal-edit.scss @@ -21,5 +21,10 @@ border-width: 0 1px; border-radius: 0; } + + } + + .error { + clear: left; } } diff --git a/app/templates/components/donation-goal-edit.hbs b/app/templates/components/donation-goal-edit.hbs index 4571031d2..b37ec7888 100644 --- a/app/templates/components/donation-goal-edit.hbs +++ b/app/templates/components/donation-goal-edit.hbs @@ -3,6 +3,9 @@ {{input class="amount" name="amount" type="number" placeholder="Custom amount" value=amount}} per month +{{#each donationGoal.errors.amount as |error|}} +

{{error.message}}

+{{/each}}
{{textarea class="description" @@ -10,6 +13,10 @@ placeholder="Tell your donors what this goal will allow you to do. Be specific, but make it interesting!" value=description}}
+{{#each donationGoal.errors.description as |error|}} +

{{error.message}}

+{{/each}} +
{{/if}} {{/if}} + {{#if error}} + {{error-formatter error=error}} + {{/if}}
diff --git a/app/utils/error-utils.js b/app/utils/error-utils.js new file mode 100644 index 000000000..70aaab68d --- /dev/null +++ b/app/utils/error-utils.js @@ -0,0 +1,19 @@ +/** + * Figures out if an error payload received from the server + * contains validation errors and thus is a validation error payload + * + * This is done by checking for presence of a `source` property in + * any of the received error objects. + * + * @param {DS.AdapterError} payload An instance of a `DS.AdapterError` + * @return {Boolean} `true` if the payload is a validaton error payload + */ +export function isValidationError(payload) { + if (!payload.isAdapterError) { + return false; + } + + let errors = payload.errors || []; + + return errors.some((e) => e.source); +} diff --git a/tests/acceptance/project-donation-goals-test.js b/tests/acceptance/project-donation-goals-test.js index 74f7904f9..7d7764ea3 100644 --- a/tests/acceptance/project-donation-goals-test.js +++ b/tests/acceptance/project-donation-goals-test.js @@ -4,6 +4,7 @@ import { authenticateAsMemberOfRole } from 'code-corps-ember/tests/helpers/authe import createOrganizationWithSluggedRoute from 'code-corps-ember/tests/helpers/mirage/create-organization-with-slugged-route'; import createProjectWithSluggedRoute from 'code-corps-ember/tests/helpers/mirage/create-project-with-slugged-route'; import projectSettingsDonationsPage from '../pages/project/settings/donations'; +import Mirage from 'ember-cli-mirage'; moduleForAcceptance('Acceptance | Project Donation Goals'); @@ -249,3 +250,81 @@ test('it does not show donation progress if donations are not active', function( assert.notOk(projectSettingsDonationsPage.donationProgress.isVisible, 'It does not show donation progress.'); }); }); + +test('it renders validation errors', function(assert) { + assert.expect(3); + + let project = createProjectWithSluggedRoute(); + let { organization } = project; + organization.createStripeConnectAccount(); + + authenticateAsMemberOfRole(this.application, server, organization, 'owner'); + + let done = assert.async(); + + server.post('donation-goals', function() { + done(); + return new Mirage.Response(422, {}, { + errors: [{ + id: 'VALIDATION_ERROR', + source: { pointer: 'data/attributes/amount' }, + detail: 'Amount is required', + status: 422 + }, { + id: 'VALIDATION_ERROR', + source: { pointer: 'data/attributes/description' }, + detail: 'Description is required', + status: 422 + }] + }); + }); + + projectSettingsDonationsPage.visit({ organization: organization.slug, project: project.slug }); + + andThen(() => { + let form = projectSettingsDonationsPage.editedDonationGoals(0); + form.clickSave(); + }); + + andThen(() => { + let form = projectSettingsDonationsPage.editedDonationGoals(0); + + assert.equal(form.validationErrors().count, 2, 'Both validation errors are rendered.'); + assert.equal(form.validationErrors(0).message, 'Amount is required'); + assert.equal(form.validationErrors(1).message, 'Description is required'); + }); +}); + +test('it renders other errors', function(assert) { + assert.expect(1); + + let project = createProjectWithSluggedRoute(); + let { organization } = project; + organization.createStripeConnectAccount(); + + authenticateAsMemberOfRole(this.application, server, organization, 'owner'); + + let done = assert.async(); + + server.post('donation-goals', function() { + done(); + return new Mirage.Response(500, {}, { + errors: [{ + id: 'INTERNAL SERVER ERROR', + title: 'Something went wrong', + detail: 'Something went wrong', + status: 500 + }] + }); + }); + + projectSettingsDonationsPage.visit({ organization: organization.slug, project: project.slug }); + + andThen(() => { + projectSettingsDonationsPage.editedDonationGoals(0).clickSave(); + }); + + andThen(() => { + assert.equal(projectSettingsDonationsPage.errorFormatter.errors().count, 1, 'The error is displayed'); + }); +}); diff --git a/tests/pages/project/settings/donations.js b/tests/pages/project/settings/donations.js index 4ef0f160c..fe1dee1be 100644 --- a/tests/pages/project/settings/donations.js +++ b/tests/pages/project/settings/donations.js @@ -4,9 +4,11 @@ import { collection, create, fillable, + text, visitable } from 'ember-cli-page-object'; import donationProgress from 'code-corps-ember/tests/pages/components/donations/donation-progress'; +import errorFormatter from 'code-corps-ember/tests/pages/components/error-formatter'; export default create({ visit: visitable(':organization/:project/settings/donations'), @@ -26,10 +28,18 @@ export default create({ amount: fillable('input[name=amount]'), description: fillable('textarea[name=description]'), clickSave: clickable('.save'), - clickCancel: clickable('.cancel') + clickCancel: clickable('.cancel'), + validationErrors: collection({ + itemScope: '.error', + item: { + message: text('') + } + }) } }), + errorFormatter, + stripeConnectButton: { scope: '.stripe-connect', href: attribute('href')