-
-
- <%= render "icons/#{@icon_name}" %>
-
-
-
-
<%= @data[:title] %>
-
<%= @data[:body] %>
-
-
+
+
+
+
+
+
+ <%= render "icons/#{@icon_name}" %>
+
+
+
+
+ <%= @data[:title] %>
+
+ <% if @data[:body].present? %>
+
+ <%= @data[:body] %>
+
+ <% end %>
+ <% if @data[:action].present? %>
+
+ <% end %>
+
+
+ <% if @data[:countdown] %>
+
+ <% end %>
diff --git a/app/components/notification_component.rb b/app/components/notification_component.rb
index a10404d..7b3bc84 100644
--- a/app/components/notification_component.rb
+++ b/app/components/notification_component.rb
@@ -1,13 +1,22 @@
# frozen_string_literal: true
+# @param type [String] Classic notification type `error`, `alert` and `info` + custom `success`
+# @param data [String, Hash] `String` for backward compatibility,
+# `Hash` for the new functionality `{title: '', body: '', timeout: 5, countdown: false, action: { url: '', method: '', name: ''}}`.
+# The `title` attribute for `Hash` is mandatory.
class NotificationComponent < ViewComponent::Base
def initialize(type:, data:)
@type = type
@data = prepare_data(data)
@icon_name = icon_name
@icon_color_class = icon_color_class
+
+ @data[:timeout] ||= 5
+ @data[:action][:method] ||= "get" if @data[:action]
end
+ private
+
def prepare_data(data)
case data
when Hash
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 81a080c..49c0764 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -3,10 +3,5 @@ class DashboardController < ApplicationController
def index
@current_section = :dashboard
- # flash[:error] = 'Notification shown successfully!'
- flash[:success] = {
- title: 'Discussion moved',
- body: 'Lorem ipsum dolor sit amet consectetur adipisicing elit oluptatum tenetur.'
- }
end
end
diff --git a/app/javascript/application.js b/app/javascript/application.js
index 1621348..0d7b494 100644
--- a/app/javascript/application.js
+++ b/app/javascript/application.js
@@ -1,3 +1,3 @@
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails"
-// import "controllers"
+import "controllers"
diff --git a/app/javascript/controllers/hello_controller.js b/app/javascript/controllers/hello_controller.js
deleted file mode 100644
index 5975c07..0000000
--- a/app/javascript/controllers/hello_controller.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import { Controller } from "@hotwired/stimulus"
-
-export default class extends Controller {
- connect() {
- this.element.textContent = "Hello World!"
- }
-}
diff --git a/app/javascript/controllers/notification_controller.js b/app/javascript/controllers/notification_controller.js
new file mode 100644
index 0000000..e0291ce
--- /dev/null
+++ b/app/javascript/controllers/notification_controller.js
@@ -0,0 +1,110 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = ["buttons", "countdown"]
+
+ connect() {
+ const timeoutSeconds = parseInt(this.data.get("timeout"));
+
+ setTimeout(() => {
+ this.element.classList.remove('hidden');
+ this.element.classList.add('notification-enter', 'notification-enter-from');
+
+ // Trigger transition
+ setTimeout(() => {
+ this.element.classList.add('notification-enter-to');
+ }, 100);
+
+ // Trigger countdown
+ if (this.hasCountdownTarget) {
+ this.countdownTarget.style.animation = 'notification-countdown linear ' + timeoutSeconds + 's';
+ }
+
+ }, 500);
+ this.timeoutId = setTimeout(() => {
+ this.close();
+ }, timeoutSeconds * 1000 + 500);
+ }
+
+ run(e) {
+ e.preventDefault();
+ this.stop();
+ let _this = this;
+ this.buttonsTarget.innerHTML = '
Processing...';
+
+ // Call the action
+ fetch(this.data.get("action-url"), {
+ method: this.data.get("action-method").toUpperCase(),
+ dataType: 'script',
+ credentials: "include",
+ headers: {
+ "X-CSRF-Token": this.csrfToken
+ },
+ })
+ .then(response => {
+ if (!response.ok) {
+ throw Error(response.statusText);
+ }
+ return response;
+ })
+ .then(response => response.json())
+ .then(data => {
+ // Set new content
+ _this.buttonsTarget.innerHTML = '
' + data.message + '';
+
+ // Remove hidden class and display the record
+ if (data.inline) {
+ document.getElementById(data.dom_id).classList.toggle('hidden');
+ }
+
+ // Close
+ setTimeout(() => {
+ if (data.inline) {
+ // Just close the notification
+ _this.close();
+ } else {
+ // Reload the page using Turbo
+ window.Turbo.visit(window.location.toString(), {action: 'replace'})
+ }
+ }, 1000);
+ })
+ .catch(error => {
+ console.log(error);
+ _this.buttonsTarget.innerHTML = '
Error!';
+ setTimeout(() => {
+ _this.close();
+ }, 1000);
+ });
+ }
+
+ stop() {
+ clearTimeout(this.timeoutId)
+ this.timeoutId = null
+ }
+
+ continue() {
+ this.timeoutId = setTimeout(() => {
+ this.close();
+ }, parseInt(this.data.get("timeout")));
+ }
+
+ close() {
+ this.element.classList.remove('notification-enter', 'notification-enter-from', 'notification-enter-to');
+ this.element.classList.add('notification-leave', 'notification-leave-from')
+
+ // Trigger transition
+ setTimeout(() => {
+ this.element.classList.add('notification-leave-to');
+ }, 100);
+
+ // Remove element after transition
+ setTimeout(() => {
+ this.element.remove();
+ }, 300);
+ }
+
+ get csrfToken() {
+ const element = document.head.querySelector('meta[name="csrf-token"]')
+ return element.getAttribute("content")
+ }
+}
diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb
index 51ad0f5..9d886dc 100644
--- a/app/views/layouts/admin.html.erb
+++ b/app/views/layouts/admin.html.erb
@@ -66,15 +66,15 @@