Merge pull request #11874 from dacook/buu-success-message-11517

[BUU] Flash message redesign
This commit is contained in:
Rachel Arnould
2023-12-13 11:59:19 +01:00
committed by GitHub
25 changed files with 226 additions and 52 deletions

View File

@@ -32,9 +32,6 @@ jQuery(function($) {
});
}
// Make flash messages dissapear
setTimeout('$(".flash").fadeOut()', 5000);
// Highlight hovered table column
$('table tbody tr td.actions a').hover(function(){
var tr = $(this).closest('tr');

View File

@@ -20,7 +20,7 @@
padding-top: 5px;
padding-bottom: 5px;
background-color: white;
@include defaultBoxShadow;
box-shadow: $box-shadow;
border-radius: 3px;
min-width: 80px;
display: none;

View File

@@ -82,8 +82,9 @@ module Spree
def clone
@new = @product.duplicate
@new.save
raise "Clone failed" unless @new.save
flash[:success] = t('.success')
redirect_to spree.admin_products_url
end

View File

@@ -38,13 +38,12 @@ class ProductsReflex < ApplicationReflex
@products = product_set.collection # use instance variable mainly for testing
if product_set.save
# flash[:success] = with_locale { I18n.t('.success') }
# morph_admin_flashes # ERROR: selector morph type has already been set
flash[:success] = I18n.t('admin.products_v3.bulk_update.success')
elsif product_set.errors.present?
@error_counts = { saved: product_set.saved_count, invalid: product_set.invalid.count }
end
render_products_form
render_products_form_with_flash
end
private
@@ -85,9 +84,10 @@ class ProductsReflex < ApplicationReflex
morph :nothing
end
def render_products_form
def render_products_form_with_flash
locals = { products: @products }
locals[:error_counts] = @error_counts if @error_counts.present?
locals[:flashes] = flash if flash.any?
cable_ready.replace(
selector: "#products-form",

View File

@@ -4,6 +4,7 @@
'data-controller': "bulk-form", 'data-bulk-form-disable-selector-value': "#sort,#filters",
'data-bulk-form-error-value': defined?(error_counts),
} do |form|
= render(partial: "admin/shared/flashes", locals: { flashes: }) if defined? flashes
%fieldset.form-actions{ class: ("hidden" unless defined?(error_counts)), 'data-bulk-form-target': "actions" }
.container
.status.eleven.columns

View File

@@ -11,7 +11,9 @@
= render partial: 'spree/admin/shared/product_sub_menu'
#products_v3_page{ "data-controller": "products" }
#loading-spinner.spinner-container{ "data-controller": "loading", "data-products-target": "loading" }
.spinner
= t('.loading')
.spinner-overlay{ "data-controller": "loading", "data-products-target": "loading" }
.spinner-container
.spinner
= t('.loading')
#products-content

View File

@@ -1,6 +1,8 @@
#flashes
.flash-container
- if defined? flashes
- flashes.each do |type, msg|
.animate-show{"data-controller": "flash"}
.animate-show{"data-controller": "flash", "data-flash-auto-close-value": type == 'success'}
.flash{type: "#{type}", class: "#{type}"}
%span= msg
.msg= msg
.actions
%button.secondary{"data-action": "click->flash#close"}= t('.dismiss')

View File

@@ -3,7 +3,7 @@
= yield :stripe_js
#wrapper
.flash-container
#flashes
= render partial: "admin/shared/flashes", locals: { flashes: flash }
= render partial: "spree/layouts/admin/progress_spinner"

View File

@@ -5,7 +5,25 @@ document.addEventListener("turbolinks:before-cache", () =>
);
export default class extends Controller {
close() {
static values = {
autoClose: Boolean,
};
connect() {
if (this.autoCloseValue) {
setTimeout(this.close.bind(this), 5000);
}
}
close(e) {
// Fade out
this.element.classList.remove("animate-show");
this.element.classList.add("animate-hide-500");
setTimeout(this.remove.bind(this), 500);
e && e.preventDefault(); // Prevent submitting if we're inside a form
}
remove() {
this.element.remove();
}
}

View File

@@ -11,13 +11,23 @@
}
@keyframes fade-out-hide {
0% {opacity: 1; visibility: visible;}
99% {opacity: 0; visibility: visible;}
100% {opacity: 0; visibility: hidden;}
0% {
opacity: 1;
visibility: visible;
}
99% {
opacity: 0;
visibility: visible;
}
100% {
opacity: 0;
visibility: hidden;
}
}
.animate-hide-500 {
animation: fade-out-hide 0.5s;
opacity: 0; // Stay invisible after animation
}
// @-webkit-keyframes slideOutDown

View File

@@ -57,6 +57,10 @@
&:nth-child(3) {
padding: 20px;
}
.actions {
display: none; /* avoid adding new button on old design */
}
}
}

View File

@@ -56,7 +56,7 @@
@import "../admin/components/dialogs";
@import "../admin/components/input";
@import "../admin/components/jquery_dialog";
@import "../admin/components/messages";
@import "components/messages"; // admin_v3
@import "components/navigation"; // admin_v3
@import "../admin/components/ng-cloak";
@import "../admin/components/page_actions";

View File

@@ -53,14 +53,14 @@ button:not(.plain):not(.trix-button),
}
&.secondary {
background-color: transparent;
background-color: $color-btn-secondary-bg;
border: 1px solid $color-btn-bg;
color: $color-btn-bg;
&:hover {
background-color: $color-11;
border: 1px solid $color-btn-hover-bg;
color: $color-btn-hover-bg;
border: 1px solid $color-btn-secondary-hover-bg;
color: $color-btn-secondary-hover-bg;
}
&:active,

View File

@@ -0,0 +1,80 @@
.errorExplanation {
padding: 10px;
border: 1px solid very-light($color-error, 12);
background-color: very-light($color-error, 6);
border-radius: 3px;
color: $color-error;
margin-bottom: 15px;
h2 {
font-size: 140%;
color: $color-error;
margin-bottom: 5px;
}
p {
padding: 10px 0;
}
ul {
list-style-position: inside;
li {
font-weight: $font-weight-bold;
}
}
}
.flash-container {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
z-index: 1000;
display: flex;
justify-content: center;
.flash {
position: relative;
display: flex;
align-items: center;
bottom: 3.5rem;
padding: 0.25rem;
min-width: 24rem;
max-width: 48.25em;
font-weight: 600;
background-color: $light-grey;
border-radius: $border-radius;
box-shadow: $box-shadow;
&.success {
color: $color-flash-success-text;
background-color: $color-flash-success-bg;
}
&.notice {
color: $color-flash-notice-text;
background-color: $color-flash-notice-bg;
border-left: $border-radius solid $red;
}
&.error {
color: $color-flash-error-text;
background-color: $color-flash-error-bg;
}
.msg {
flex-grow: 1;
margin: 0.25rem 0.75rem;
}
}
}
.notice:not(.flash) {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: $spree-light-blue;
border-radius: $border-radius;
a {
font-weight: bold;
}
}

View File

@@ -61,7 +61,7 @@ nav.menu {
}
#admin-menu {
@include defaultBoxShadow;
box-shadow: $box-shadow;
li {
.dropdown {

View File

@@ -20,7 +20,7 @@
display: inline-block;
text-align: center;
background-color: $color-1;
@include defaultBoxShadow;
box-shadow: $box-shadow;
border-radius: 4px;
border: none;
color: $color-8;
@@ -77,7 +77,7 @@
background-color: $white;
color: $near-black;
box-shadow: $color-btn-shadow;
box-shadow: $box-shadow;
&.active {
color: $white;

View File

@@ -1,16 +1,23 @@
.spinner-container {
.spinner-overlay {
position: absolute;
width: 100%;
left: 0;
right: 0;
height: 100%;
min-height: 200px;
background: rgba(255, 255, 255, 0.8);
z-index: 2;
}
.spinner-container {
position: fixed;
left: 0;
right: 0;
display: flex;
justify-content: flex-start;
justify-content: center;
align-items: center;
flex-direction: column;
gap: 40px;
font-size: 24px;
background: rgba(255, 255, 255, 0.8);
z-index: 2;
&.hidden {
display: none;

View File

@@ -25,6 +25,12 @@ $color-success: $green !default;
$color-notice: $yellow !default;
$color-warning: $red !default;
$color-error: $red !default;
$color-flash-success-text: $white !default;
$color-flash-success-bg: $near-black !default;
$color-flash-notice-text: $color-body-text !default;
$color-flash-notice-bg: $fair-pink !default;
$color-flash-error-text: $white !default;
$color-flash-error-bg: $color-error !default;
// Table styles
$color-tbl-bg: $light-grey !default;
@@ -40,7 +46,6 @@ $padding-tbl-cell-relaxed: 12px 12px;
// Button colors
$color-btn-bg: $teal !default;
$color-btn-text: $white !default;
$color-btn-shadow: 0px 1px 0px rgba(0, 0, 0, 0.05), 0px 2px 2px rgba(0, 0, 0, 0.07) !default;
$color-btn-hover-bg: $orient !default;
$color-btn-hover-text: $white !default;
$color-btn-hover-border: $dark-blue !default;
@@ -48,6 +53,8 @@ $color-btn-disabled-bg: $medium-grey !default;
$color-btn-disabled-text: $lighter-grey !default;
$color-btn-red-bg: $red !default;
$color-btn-red-hover-bg: $roof-terracotta !default;
$color-btn-secondary-bg: $white !default;
$color-btn-secondary-hover-bg: $orient !default;
// Actions colors
$color-action-edit-bg: very-light($teal) !default;
@@ -161,6 +168,7 @@ $h2-size: $h3-size + 2 !default;
$h1-size: $h2-size + 2 !default;
$border-radius: 4px !default;
$box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.05), 0px 2px 2px rgba(0, 0, 0, 0.07) !default;
$font-weight-bold: 600 !default;
$font-weight-normal: 400 !default;

View File

@@ -1,7 +1,3 @@
@mixin defaultBoxShadow {
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.05), 0px 2px 2px rgba(0, 0, 0, 0.07);
}
@mixin arrowDown {
content: " ";
display: block;

View File

@@ -266,11 +266,11 @@ fieldset {
}
.form-actions {
@include defaultBoxShadow;
box-shadow: $box-shadow;
background-color: $fair-pink;
border: none;
border-left: 4px solid $red;
border-radius: 4px;
border-left: $border-radius solid $red;
border-radius: $border-radius;
margin: 0.5em 0;
padding: 0;

View File

@@ -851,8 +851,8 @@ en:
other: "%{count} products could not be saved. Please review the errors and try again."
save: Save changes
reset: Discard changes
bulk_update: # TODO: fix these
success: "Products successfully updated" #TODO: add count
bulk_update:
success: Changes saved
product_import:
title: Product Import
file_not_found: File not found or could not be opened
@@ -1510,6 +1510,8 @@ en:
has_no_payment_methods: "%{enterprise} has no payment methods"
has_no_shipping_methods: "%{enterprise} has no shipping methods"
has_no_enterprise_fees: "%{enterprise} has no enterprise fees"
flashes:
dismiss: Dismiss
side_menu:
enterprise:
primary_details: "Primary Details"
@@ -4341,6 +4343,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using
bulk_unit_size: Bulk unit size
display_as:
display_as: Display As
clone:
success: Product cloned
reports:
table:
select_and_search: "Select filters and click on %{option} to access your data."

View File

@@ -0,0 +1,34 @@
/**
* @jest-environment jsdom
*/
import { Application } from "stimulus";
import flash_controller from "../../../app/webpacker/controllers/flash_controller";
describe("FlashController", () => {
beforeAll(() => {
const application = Application.start();
application.register("flash", flash_controller);
});
beforeEach(() => {
document.body.innerHTML = `
<div id="element" data-controller='flash' data-flash-auto-close-value='true'></div>
`;
});
describe("autoClose", () => {
jest.useFakeTimers();
it("is cleared after about 5 seconds", () => {
let element = document.getElementById("element");
expect(element).not.toBe(null);
jest.advanceTimersByTime(5500);
element = document.getElementById("element");
expect(element).toBe(null);
});
});
});

View File

@@ -7,6 +7,12 @@ describe ProductsReflex, type: :reflex, feature: :admin_style_v3 do
let(:context) {
{ url: admin_products_url, connection: { current_user: } }
}
let(:flash) { {} }
before do
# Mock flash, because stimulus_reflex_testing doesn't support sessions
allow_any_instance_of(described_class).to receive(:flash).and_return(flash)
end
describe '#fetch' do
subject{ build_reflex(method_name: :fetch, **context) }
@@ -54,6 +60,8 @@ describe ProductsReflex, type: :reflex, feature: :admin_style_v3 do
product_a.reload
}.to change{ product_a.name }.to("Pommes")
.and change{ product_a.sku }.to("POM-00")
expect(flash).to include success: "Changes saved"
end
it "saves valid changes to products and nested variants" do
@@ -89,6 +97,8 @@ describe ProductsReflex, type: :reflex, feature: :admin_style_v3 do
.and change{ variant_a1.sku }.to("POM-01")
.and change{ variant_a1.price }.to(10.25)
.and change{ variant_a1.on_hand }.to(6)
expect(flash).to include success: "Changes saved"
end
describe "sorting" do
@@ -112,6 +122,7 @@ describe ProductsReflex, type: :reflex, feature: :admin_style_v3 do
product_a.id,
product_b.id,
]
expect(flash).to include success: "Changes saved"
end
end
@@ -136,6 +147,7 @@ describe ProductsReflex, type: :reflex, feature: :admin_style_v3 do
reflex = run_reflex(:bulk_update, params:)
expect(reflex.get(:error_counts)).to eq({ saved: 1, invalid: 2 })
expect(flash).to_not include success: "Changes saved"
# # WTF
# expect{ reflex(:bulk_update, params:) }.to broadcast(

View File

@@ -19,7 +19,7 @@ module WebHelper
end
def flash_message
find('.flash', visible: false).text(:all).strip
find('.flash .msg', visible: false).text(:all).strip
end
def handle_js_confirm(accept = true)

View File

@@ -237,7 +237,6 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
expect(page).to have_css "button[aria-label='On Hand']", text: "6"
end
pending
expect(page).to have_content "Changes saved"
end
@@ -259,7 +258,6 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
expect(page).to have_css "button[aria-label='On Hand']", text: "On demand"
end
pending
expect(page).to have_content "Changes saved"
end
@@ -298,14 +296,15 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
fill_in "Name", with: ""
fill_in "SKU", with: "A" * 256
end
end
it "shows errors for both product and variant fields" do
# Update variant with invalid data too
within row_containing_name("Medium box") do
fill_in "Name", with: "L" * 256
fill_in "SKU", with: "1" * 256
fill_in "Price", with: "10.25"
end
end
it "shows errors for both product and variant fields" do
# Also update another product with valid data
within row_containing_name("Bananas") do
fill_in "Name", with: "Bananes"
@@ -316,9 +315,7 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
product_a.reload
}.to_not change { product_a.name }
# pending("unchanged rows are being saved") # TODO: don't report unchanged rows
# expect(page).to_not have_content("rows were saved correctly")
# Both the product and variant couldn't be saved.
expect(page).to have_content("1 product was saved correctly")
expect(page).to have_content("1 product could not be saved")
expect(page).to have_content "Please review the errors and try again"
@@ -352,7 +349,6 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
}.to change { product_a.name }.to("Pommes")
.and change{ product_a.sku }.to("POM-00")
pending
expect(page).to have_content "Changes saved"
end
end
@@ -401,6 +397,8 @@ describe 'As an admin, I can see the new product page', feature: :admin_style_v3
click_link('Clone')
end
expect(page).to have_content "Product cloned"
within "table.products" do
# Gather input values, because page.content doesn't include them.
input_content = page.find_all('input[type=text]').map(&:value).join