diff --git a/ghost/admin/app/components/modal-delete-webhook.hbs b/ghost/admin/app/components/modal-delete-webhook.hbs deleted file mode 100644 index 3e91094906..0000000000 --- a/ghost/admin/app/components/modal-delete-webhook.hbs +++ /dev/null @@ -1,15 +0,0 @@ - -{{svg-jar "close"}} - - - - \ No newline at end of file diff --git a/ghost/admin/app/components/modal-delete-webhook.js b/ghost/admin/app/components/modal-delete-webhook.js deleted file mode 100644 index b39eeb90e3..0000000000 --- a/ghost/admin/app/components/modal-delete-webhook.js +++ /dev/null @@ -1,26 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import {alias} from '@ember/object/computed'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default ModalComponent.extend({ - notifications: service(), - - webhook: alias('model'), - - actions: { - confirm() { - this.deleteWebhook.perform(); - } - }, - - deleteWebhook: task(function* () { - try { - yield this.confirm(); - } catch (error) { - this.notifications.showAPIError(error, {key: 'webhook.delete.failed'}); - } finally { - this.send('closeModal'); - } - }).drop() -}); diff --git a/ghost/admin/app/components/modal-webhook-form.hbs b/ghost/admin/app/components/modal-webhook-form.hbs deleted file mode 100644 index a4dcf2f95d..0000000000 --- a/ghost/admin/app/components/modal-webhook-form.hbs +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - diff --git a/ghost/admin/app/components/modal-webhook-form.js b/ghost/admin/app/components/modal-webhook-form.js deleted file mode 100644 index 51f183ee32..0000000000 --- a/ghost/admin/app/components/modal-webhook-form.js +++ /dev/null @@ -1,80 +0,0 @@ -import ModalComponent from 'ghost-admin/components/modal-base'; -import Webhook from 'ghost-admin/models/webhook'; -import {AVAILABLE_EVENTS} from 'ghost-admin/helpers/event-name'; -import {alias} from '@ember/object/computed'; -import {camelize} from '@ember/string'; -import {inject as service} from '@ember/service'; -import {task} from 'ember-concurrency'; - -export default ModalComponent.extend({ - router: service(), - feature: service(), - - availableEvents: null, - error: null, - buttonText: 'Save', - successText: 'Saved', - - confirm() {}, - - webhook: alias('model'), - - init() { - this._super(...arguments); - this.availableEvents = AVAILABLE_EVENTS; - }, - - didReceiveAttrs() { - this._super(...arguments); - if (this.webhook.isNew) { - this.set('buttonText', 'Create'); - this.set('successText', 'Created'); - } - }, - - actions: { - selectEvent(value) { - this.webhook.set('event', value); - this.webhook.validate({property: 'event'}); - }, - - confirm() { - this.saveWebhook.perform(); - } - }, - - saveWebhook: task(function* () { - this.set('error', null); - - try { - let webhook = yield this.confirm(); - let integration = yield webhook.get('integration'); - this.router.transitionTo('settings.integration', integration); - } catch (e) { - // TODO: server-side validation errors should be serialized - // properly so that errors are added to model.errors automatically - if (e && e.payload && e.payload.errors) { - let attrs = Array.from(Webhook.attributes.keys()); - - e.payload.errors.forEach((error) => { - let {message, property = ''} = error; - property = camelize(property); - - if (property && attrs.includes(property)) { - this.webhook.errors.add(property, message); - this.webhook.hasValidated.pushObject(property); - } else { - this.set('error', `Error: ${message}`); - } - }); - - return; - } - - // bubble up to the global error handler - if (e) { - throw e; - } - } - }) -}); diff --git a/ghost/admin/app/components/settings/integrations/delete-webhook-modal.hbs b/ghost/admin/app/components/settings/integrations/delete-webhook-modal.hbs new file mode 100644 index 0000000000..7f1b797923 --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/delete-webhook-modal.hbs @@ -0,0 +1,17 @@ + \ No newline at end of file diff --git a/ghost/admin/app/components/settings/integrations/delete-webhook-modal.js b/ghost/admin/app/components/settings/integrations/delete-webhook-modal.js new file mode 100644 index 0000000000..5c0563ccd6 --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/delete-webhook-modal.js @@ -0,0 +1,28 @@ +import Component from '@glimmer/component'; +import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; + +export default class DeleteWebhookModal extends Component { + @service notifications; + @service router; + + @task({drop: true}) + *deleteWebhookTask() { + try { + const {webhook} = this.args.data; + + if (webhook.isDeleted) { + return true; + } + + yield webhook.destroyRecord(); + + this.notifications.closeAlerts('webhook.delete'); + return true; + } catch (error) { + this.notifications.showAPIError(error, {key: 'webhook.delete.failed'}); + } finally { + this.args.close(); + } + } +} diff --git a/ghost/admin/app/components/settings/integrations/webhook-form-modal.hbs b/ghost/admin/app/components/settings/integrations/webhook-form-modal.hbs new file mode 100644 index 0000000000..4d1861ed4e --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/webhook-form-modal.hbs @@ -0,0 +1,110 @@ + \ No newline at end of file diff --git a/ghost/admin/app/components/settings/integrations/webhook-form-modal.js b/ghost/admin/app/components/settings/integrations/webhook-form-modal.js new file mode 100644 index 0000000000..de556bed0f --- /dev/null +++ b/ghost/admin/app/components/settings/integrations/webhook-form-modal.js @@ -0,0 +1,81 @@ +import Component from '@glimmer/component'; +import Webhook from 'ghost-admin/models/webhook'; +import {AVAILABLE_EVENTS} from 'ghost-admin/helpers/event-name'; +import {action} from '@ember/object'; +import {camelize} from '@ember/string'; +import {inject as service} from '@ember/service'; +import {task} from 'ember-concurrency'; +import {tracked} from '@glimmer/tracking'; + +export default class WebhookFormModal extends Component { + @service notifications; + @service router; + + availableEvents = AVAILABLE_EVENTS; + buttonText = this.args.data.webhook.isNew ? 'Create' : 'Save'; + successText = this.args.data.webhook.isNew ? 'Created' : 'Saved'; + + @tracked error = null; + + get webhook() { + return this.args.data.webhook; + } + + @action + setProperty(property, event) { + this.webhook[property] = event.target.value; + } + + @action + selectEvent(value) { + this.webhook.event = value; + this.webhook.validate({property: 'event'}); + } + + @action + validate(property) { + return this.webhook.validate({property}); + } + + @action + noop(event) { + event.preventDefault(); + } + + @task({drop: true}) + *saveWebhookTask() { + this.error = null; + + try { + const webhook = yield this.webhook.save(); + const integration = yield webhook.integration; + this.router.transitionTo('settings.integration', integration); + return true; + } catch (e) { + // TODO: server-side validation errors should be serialized + // properly so that errors are added to model.errors automatically + if (e && e.payload && e.payload.errors) { + const attrs = Array.from(Webhook.attributes.keys()); + + e.payload.errors.forEach((error) => { + let {message, property = ''} = error; + property = camelize(property); + + if (property && attrs.includes(property)) { + this.webhook.errors.add(property, message); + this.webhook.hasValidated.pushObject(property); + } else { + this.error = `Error: ${message}`; + } + }); + + return; + } + + // bubble up to the global error handler + if (e) { + throw e; + } + } + } +} diff --git a/ghost/admin/app/controllers/settings/integration.js b/ghost/admin/app/controllers/settings/integration.js index 0fbd69085d..a25e029d61 100644 --- a/ghost/admin/app/controllers/settings/integration.js +++ b/ghost/admin/app/controllers/settings/integration.js @@ -1,5 +1,6 @@ import Controller from '@ember/controller'; import DeleteIntegrationModal from '../../components/settings/integrations/delete-integration-modal'; +import DeleteWebhookModal from '../../components/settings/integrations/delete-webhook-modal'; import config from 'ghost-admin/config/environment'; import copyTextToClipboard from 'ghost-admin/utils/copy-text-to-clipboard'; import { @@ -24,7 +25,6 @@ export default class IntegrationController extends Controller { @tracked showUnsavedChangesModal = false; @tracked selectedApiKey = null; @tracked isApiKeyRegenerated = false; - @tracked webhookToDelete; constructor() { super(...arguments); @@ -180,13 +180,7 @@ export default class IntegrationController extends Controller { @action confirmWebhookDeletion(webhook, event) { event?.preventDefault(); - this.webhookToDelete = webhook; - } - - @action - cancelWebhookDeletion(event) { - event?.preventDefault(); - this.webhookToDelete = null; + return this.modals.open(DeleteWebhookModal, {webhook}); } @action diff --git a/ghost/admin/app/controllers/settings/integration/webhooks/edit.js b/ghost/admin/app/controllers/settings/integration/webhooks/edit.js index 6e33dcf3c4..a1c986ec4f 100644 --- a/ghost/admin/app/controllers/settings/integration/webhooks/edit.js +++ b/ghost/admin/app/controllers/settings/integration/webhooks/edit.js @@ -1,28 +1,4 @@ import Controller from '@ember/controller'; -import classic from 'ember-classic-decorator'; -import {action} from '@ember/object'; -import {alias} from '@ember/object/computed'; -@classic export default class EditController extends Controller { - @alias('model') - webhook; - - @action - save() { - return this.webhook.save(); - } - - @action - cancel() { - // 'new' route's dectivate hook takes care of rollback - return this.webhook.get('integration').then((integration) => { - this.transitionToRoute('settings.integration', integration); - }); - } - - reset() { - this.webhook.rollbackAttributes(); - this.webhook.errors.clear(); - } } diff --git a/ghost/admin/app/controllers/settings/integration/webhooks/new.js b/ghost/admin/app/controllers/settings/integration/webhooks/new.js index 2032e2f2d0..2c1d6ff758 100644 --- a/ghost/admin/app/controllers/settings/integration/webhooks/new.js +++ b/ghost/admin/app/controllers/settings/integration/webhooks/new.js @@ -1,23 +1,4 @@ import Controller from '@ember/controller'; -import classic from 'ember-classic-decorator'; -import {action} from '@ember/object'; -import {alias} from '@ember/object/computed'; -@classic -export default class NewController extends Controller { - @alias('model') - webhook; - - @action - save() { - return this.webhook.save(); - } - - @action - cancel() { - // 'new' route's dectivate hook takes care of rollback - return this.webhook.get('integration').then((integration) => { - this.transitionToRoute('settings.integration', integration); - }); - } +export default class NewWebhookController extends Controller { } diff --git a/ghost/admin/app/routes/settings/integration/webhooks/edit.js b/ghost/admin/app/routes/settings/integration/webhooks/edit.js index b20ee342b7..4d6e9cd48b 100644 --- a/ghost/admin/app/routes/settings/integration/webhooks/edit.js +++ b/ghost/admin/app/routes/settings/integration/webhooks/edit.js @@ -1,14 +1,49 @@ import AdminRoute from 'ghost-admin/routes/admin'; +import WebhookFormModal from '../../../../components/settings/integrations/webhook-form-modal'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class EditWebhookRoute extends AdminRoute { + @service modals; + @service router; + + webhook = null; + modal = null; + + get integration() { + return this.modelFor('settings.integration'); + } -export default class EditRoute extends AdminRoute { model(params) { - let integration = this.modelFor('settings.integration'); - let webhook = integration.webhooks.findBy('id', params.webhook_id); - return webhook; + return this.integration.webhooks.findBy('id', params.webhook_id); + } + + setupController(controller, model) { + this.webhook = model; + + this.modal = this.modals.open(WebhookFormModal, { + webhook: this.webhook + }, { + beforeClose: this.beforeModalClose + }); } deactivate() { - super.deactivate(...arguments); - this.controller.reset(); + this.webhook?.errors.clear(); + this.webhook?.rollbackAttributes(); + + // ensure we don't try to redirect on modal close if we're already transitioning away + this.isLeaving = true; + this.modal?.close(); + + this.modal = null; + this.isLeaving = false; + } + + @action + beforeModalClose() { + if (this.modal && !this.isLeaving) { + this.router.transitionTo('settings.integration', this.integration); + } } } diff --git a/ghost/admin/app/routes/settings/integration/webhooks/new.js b/ghost/admin/app/routes/settings/integration/webhooks/new.js index ce881bf0a0..f972b4f1d4 100644 --- a/ghost/admin/app/routes/settings/integration/webhooks/new.js +++ b/ghost/admin/app/routes/settings/integration/webhooks/new.js @@ -1,13 +1,45 @@ import AdminRoute from 'ghost-admin/routes/admin'; +import WebhookFormModal from '../../../../components/settings/integrations/webhook-form-modal'; +import {action} from '@ember/object'; +import {inject as service} from '@ember/service'; + +export default class NewWebhookRoute extends AdminRoute { + @service modals; + @service router; + + webhook = null; + modal = null; + + get integration() { + return this.modelFor('settings.integration'); + } -export default class NewRoute extends AdminRoute { model() { - let integration = this.modelFor('settings.integration'); - return this.store.createRecord('webhook', {integration}); + this.webhook = this.store.createRecord('webhook', {integration: this.integration}); + + this.modal = this.modals.open(WebhookFormModal, { + webhook: this.webhook + }, { + beforeClose: this.beforeModalClose + }); } deactivate() { - super.deactivate(...arguments); - this.controller.webhook.rollbackAttributes(); + this.webhook?.errors.clear(); + this.webhook?.rollbackAttributes(); + + // ensure we don't try to redirect on modal close if we're already transitioning away + this.isLeaving = true; + this.modal?.close(); + + this.modal = null; + this.isLeaving = false; + } + + @action + beforeModalClose() { + if (this.modal && !this.isLeaving) { + this.router.transitionTo('settings.integration', this.integration); + } } } diff --git a/ghost/admin/app/templates/settings/integration.hbs b/ghost/admin/app/templates/settings/integration.hbs index aa2692f199..2034404499 100644 --- a/ghost/admin/app/templates/settings/integration.hbs +++ b/ghost/admin/app/templates/settings/integration.hbs @@ -290,11 +290,4 @@ @modifier="action wide" /> {{/if}} -{{#if this.webhookToDelete}} - -{{/if}} - {{outlet}} diff --git a/ghost/admin/app/templates/settings/integration/webhooks/edit.hbs b/ghost/admin/app/templates/settings/integration/webhooks/edit.hbs deleted file mode 100644 index 20fb534383..0000000000 --- a/ghost/admin/app/templates/settings/integration/webhooks/edit.hbs +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file diff --git a/ghost/admin/app/templates/settings/integration/webhooks/new.hbs b/ghost/admin/app/templates/settings/integration/webhooks/new.hbs deleted file mode 100644 index 20fb534383..0000000000 --- a/ghost/admin/app/templates/settings/integration/webhooks/new.hbs +++ /dev/null @@ -1,5 +0,0 @@ - \ No newline at end of file