diff --git a/app/controllers/admin/broadcasts_controller.rb b/app/controllers/admin/broadcasts_controller.rb index 15d085b37..1feca5d33 100644 --- a/app/controllers/admin/broadcasts_controller.rb +++ b/app/controllers/admin/broadcasts_controller.rb @@ -50,11 +50,9 @@ module Admin @broadcast = Broadcast.find(params[:id]) if @broadcast.destroy - flash[:success] = "Broadcast has been deleted!" - redirect_to admin_broadcasts_path + render json: { message: "Broadcast has been deleted!" }, status: :ok else - flash[:danger] = "Something went wrong with deleting the broadcast." - render :edit + render json: { error: "Something went wrong with deleting the broadcast." }, status: :unprocessable_entity end end diff --git a/app/javascript/admin/controllers/alert_controller.js b/app/javascript/admin/controllers/alert_controller.js new file mode 100644 index 000000000..5b26c0f58 --- /dev/null +++ b/app/javascript/admin/controllers/alert_controller.js @@ -0,0 +1,50 @@ +import { Controller } from 'stimulus'; + +export default class AlertController extends Controller { + static targets = ['alertZone']; + + closeAlert() { + this.alertZoneTarget.innerHTML = ''; + } + + alertMarkup({ alertMsg, classes }) { + return ` +
+
${alertMsg}
+ + Close + + +
+ `; + } + + generateErrorAlert(event) { + const { alertMsg } = event.detail; + + this.alertZoneTarget.innerHTML = this.alertMarkup({ + alertMsg, + classes: 'crayons-notice--danger', + }); + } + + generateSuccessAlert(event) { + const { alertMsg } = event.detail; + + this.alertZoneTarget.innerHTML = this.alertMarkup({ + alertMsg, + classes: 'crayons-notice--success', + }); + } +} diff --git a/app/javascript/admin/controllers/confirmation_modal_controller.js b/app/javascript/admin/controllers/confirmation_modal_controller.js index ae9b42ce8..61348b67e 100644 --- a/app/javascript/admin/controllers/confirmation_modal_controller.js +++ b/app/javascript/admin/controllers/confirmation_modal_controller.js @@ -4,11 +4,29 @@ import { displayErrorAlert, displaySnackbar } from '../messageUtilities'; const confirmationText = (username) => `My username is @${username} and this action is 100% safe and appropriate.`; +window.addEventListener('load', () => { + const params = new URLSearchParams(window.location.search); + + if (params.has('redirected') && localStorage.getItem('outcome') !== null) { + displaySnackbar(localStorage.getItem('outcome')); + localStorage.removeItem('outcome'); + } +}); + export default class ConfirmationModalController extends ModalController { static targets = ['itemId', 'username', 'endpoint']; - removeBadgeAchievement(id) { - return document.querySelector(`[data-row-id="${id}"]`).remove(); + handleRecord({ endpoint, id, outcome }) { + switch (endpoint) { + case '/admin/content_manager/badge_achievements': + document.querySelector(`[data-row-id="${id}"]`).remove(); + displaySnackbar(outcome.message); + break; + case '/admin/advanced/broadcasts': + localStorage.setItem('outcome', outcome.message); + window.location.replace(`${endpoint}?redirected`); + break; + } } async sendToEndpoint({ itemId, endpoint }) { @@ -27,8 +45,11 @@ export default class ConfirmationModalController extends ModalController { const outcome = await response.json(); if (response.ok) { - this.removeBadgeAchievement(itemId); - displaySnackbar(outcome.message); + this.handleRecord({ + endpoint, + id: itemId, + outcome, + }); } else { displayErrorAlert(outcome.error); } diff --git a/app/javascript/admin/controllers/error_controller.js b/app/javascript/admin/controllers/error_controller.js deleted file mode 100644 index e933708e8..000000000 --- a/app/javascript/admin/controllers/error_controller.js +++ /dev/null @@ -1,34 +0,0 @@ -import { Controller } from 'stimulus'; - -export default class ErrorController extends Controller { - static targets = ['errorZone']; - - closeErrorAlert() { - this.errorZoneTarget.innerHTML = ''; - } - - generateErrorAlert(event) { - const { errMsg } = event.detail; - - this.errorZoneTarget.innerHTML = ` -
-
${errMsg}
- - Close - - -
- `; - } -} diff --git a/app/javascript/admin/messageUtilities.js b/app/javascript/admin/messageUtilities.js index 418ae0741..ef3ab322d 100644 --- a/app/javascript/admin/messageUtilities.js +++ b/app/javascript/admin/messageUtilities.js @@ -1,15 +1,15 @@ /** * A function to generate an error alert within the /admin/ space. * - * @function errorAlert + * @function displayErrorAlert * @param {Object} modalProps Properties of the Error Alert - * @param {string} modalProps.errMsg The error message displayed within the alert. + * @param {string} modalProps.alertMsg The message displayed within the alert. */ -export const displayErrorAlert = function (errMsg) { +export const displayErrorAlert = function (alertMsg) { return document.dispatchEvent( new CustomEvent('error:generate', { - detail: { errMsg }, + detail: { alertMsg }, }), ); }; diff --git a/app/views/admin/badge_achievements/index.html.erb b/app/views/admin/badge_achievements/index.html.erb index 94466903d..d00e60020 100644 --- a/app/views/admin/badge_achievements/index.html.erb +++ b/app/views/admin/badge_achievements/index.html.erb @@ -55,37 +55,5 @@ <% end %> -
- - + <%= render partial: "admin/shared/destroy_confirmation_modal" %> diff --git a/app/views/admin/broadcasts/show.html.erb b/app/views/admin/broadcasts/show.html.erb index c74cb214f..a733885f4 100644 --- a/app/views/admin/broadcasts/show.html.erb +++ b/app/views/admin/broadcasts/show.html.erb @@ -1,47 +1,61 @@ -
-

<%= @broadcast.title %>

-
- <%= link_to "Destroy", admin_broadcast_path(@broadcast), class: "btn btn-danger", method: :delete, data: { confirm: "Are you sure?" } %> - <%= link_to "Edit", edit_admin_broadcast_path, class: "btn btn-primary" %> -
-
+
+
+

<%= @broadcast.title %>

+
+ <%= link_to "Edit", edit_admin_broadcast_path, class: "crayons-btn" %> + +
+
-
-
-

Type

-

<%= @broadcast.type_of %>

+
+
+

Type

+

<%= @broadcast.type_of %>

+
+ +
+

Content

+

<%= @broadcast.processed_html %>

+
+ +
+

Last active

+

<%= @broadcast.active_status_updated_at&.strftime("%b %d, %Y %H:%M UTC") %>

+
+ +
+

Status

+

+ "> + <%= @broadcast.active? ? "Active" : "Inactive" %> + +

+
-
-

Content

-

<%= @broadcast.processed_html %>

-
+ <% if @broadcast.processed_html %> +
+

Preview

+ +

Please note: announcement broadcasts will render directly below the nav bar once activated.

+
+ <% end %> -
-

Last active

-

<%= @broadcast.active_status_updated_at&.strftime("%b %d, %Y %H:%M UTC") %>

-
- -
-

Status

-

- "> - <%= @broadcast.active? ? "Active" : "Inactive" %> - -

-
+ <%= render partial: "admin/shared/destroy_confirmation_modal" %>
-<% if @broadcast.processed_html %> -
-

Preview

- -

Please note: announcement broadcasts will render directly below the nav bar once activated.

-
-<% end %> - <%= csrf_meta_tags %> diff --git a/app/views/admin/shared/_destroy_confirmation_modal.html.erb b/app/views/admin/shared/_destroy_confirmation_modal.html.erb new file mode 100644 index 000000000..e2813e1a8 --- /dev/null +++ b/app/views/admin/shared/_destroy_confirmation_modal.html.erb @@ -0,0 +1,33 @@ +
+ + diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index efb1116e8..8afc1c0d9 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -85,10 +85,10 @@ <% end %>
-
+ data-controller="alert" + data-action="error:generate@document->alert#generateErrorAlert" + data-testid="alertzone"> +
<% if request.path.split("/")[-3] == "admin" %> diff --git a/cypress/integration/seededFlows/adminFlows/badges/badgeAchievements.spec.js b/cypress/integration/seededFlows/adminFlows/badges/badgeAchievements.spec.js index 0efbd5b00..0f1c5b4fd 100644 --- a/cypress/integration/seededFlows/adminFlows/badges/badgeAchievements.spec.js +++ b/cypress/integration/seededFlows/adminFlows/badges/badgeAchievements.spec.js @@ -72,7 +72,7 @@ describe('Badge Achievements', () => { cy.findByRole('button', { name: 'Confirm changes' }).click(); }); - cy.findByTestId('errorzone').within(() => { + cy.findByTestId('alertzone').within(() => { cy.findByRole('alert') .contains('Something went wrong.') .should('be.visible'); diff --git a/cypress/integration/seededFlows/adminFlows/broadcasts/broadcasts.spec.js b/cypress/integration/seededFlows/adminFlows/broadcasts/broadcasts.spec.js new file mode 100644 index 000000000..0ccb69c82 --- /dev/null +++ b/cypress/integration/seededFlows/adminFlows/broadcasts/broadcasts.spec.js @@ -0,0 +1,94 @@ +describe('Broadcasts', () => { + beforeEach(() => { + cy.testSetup(); + cy.fixture('users/adminUser.json').as('user'); + + cy.get('@user').then((user) => { + cy.loginAndVisit(user, '/admin/advanced/broadcasts'); + + cy.findByRole('table').within(() => { + cy.findByRole('link', { name: 'Mock Broadcast' }).click(); + }); + + cy.findByRole('button', { name: /Destroy/i }).click(); + }); + }); + + describe('delete a broadcast', () => { + it('should display confirmation modal', () => { + cy.findByRole('dialog').contains('Confirm changes').should('be.visible'); + }); + + it('should display warning text if confirmation text does not match', () => { + cy.findByRole('dialog').within(() => { + cy.get('input').type('Text that does not match.'); + cy.findByRole('button', { name: 'Confirm changes' }).click(); + + cy.get('.crayons-notice') + .contains('The confirmation text did not match.') + .should('be.visible'); + + cy.get('button[aria-label="Close"]').click(); + }); + + cy.findByRole('heading', { level: 2, name: 'Mock Broadcast' }).should( + 'be.visible', + ); + }); + + it('should delete the broadcast if confirmation text matches', () => { + cy.get('@user').then((user) => { + cy.findByRole('dialog').within(() => { + cy.get('input').type( + `My username is @${user.username} and this action is 100% safe and appropriate.`, + ); + cy.findByRole('button', { name: 'Confirm changes' }).click(); + }); + + // testing the redirect after broadcast destroy + cy.url().should('include', '/admin/advanced/broadcasts?redirected'); + + cy.findByTestId('snackbar').within(() => { + cy.findByRole('alert').should( + 'have.text', + 'Broadcast has been deleted!', + ); + }); + + cy.findByRole('table').within(() => { + cy.findByRole('link', { name: 'Mock Broadcast' }).should('not.exist'); + }); + }); + }); + + it.skip('generates error message when destroy action fails', () => { + cy.intercept('DELETE', '/admin/advanced/broadcasts/**', { + statusCode: 422, + body: { + error: 'Something went wrong with deleting the broadcast.', + }, + }); + + cy.get('@user').then((user) => { + cy.findByRole('dialog').within(() => { + cy.get('input').type( + `My username is @${user.username} and this action is 100% safe and appropriate.`, + ); + cy.findByRole('button', { name: 'Confirm changes' }).click(); + }); + + cy.findByTestId('alertzone').within(() => { + cy.findByRole('alert') + .contains('Something went wrong with deleting the broadcast.') + .should('be.visible'); + }); + + cy.url().should('not.include', '?redirected'); + + cy.findByRole('heading', { level: 2, name: 'Mock Broadcast' }).should( + 'be.visible', + ); + }); + }); + }); +}); diff --git a/spec/requests/admin/broadcasts_spec.rb b/spec/requests/admin/broadcasts_spec.rb index d7a24849f..a0309b6e8 100644 --- a/spec/requests/admin/broadcasts_spec.rb +++ b/spec/requests/admin/broadcasts_spec.rb @@ -69,7 +69,6 @@ RSpec.describe "/admin/advanced/broadcasts", type: :request do expect do delete admin_broadcast_path(broadcast.id) end.to change { Broadcast.all.count }.by(-1) - expect(response.body).to redirect_to admin_broadcasts_path end end end @@ -101,7 +100,6 @@ RSpec.describe "/admin/advanced/broadcasts", type: :request do expect do delete admin_broadcast_path(broadcast.id) end.to change { Broadcast.all.count }.by(-1) - expect(response.body).to redirect_to admin_broadcasts_path end end end diff --git a/spec/support/seeds/seeds_e2e.rb b/spec/support/seeds/seeds_e2e.rb index 33a97aaf9..2de5362a2 100644 --- a/spec/support/seeds/seeds_e2e.rb +++ b/spec/support/seeds/seeds_e2e.rb @@ -610,3 +610,15 @@ seeder.create_if_none(FeedbackMessage) do category: :bug, ) end + +############################################################################## + +seeder.create_if_none(Broadcast) do + Broadcast.create!( + title: "Mock Broadcast", + processed_html: "

#{Faker::Hipster.paragraph(sentence_count: 2)}

", + type_of: "Welcome", + banner_style: "default", + active: true, + ) +end