feat: add flash message functionality
- Implement Stimulus controller for closing flash messages - Replace inline alerts with a partial for better organization - Enhance styling for user registration and login forms This update introduces a new flash message component that allows for user notifications to be displayed on the screen and closed by the user. The forms also include improved styles for a better user experience.
This commit is contained in:
parent
cba76e718f
commit
0312383bc8
8
app/javascript/controllers/flash_controller.js
Normal file
8
app/javascript/controllers/flash_controller.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
export default class extends Controller {
|
||||
close(event) {
|
||||
const target = event.target.closest('.alert')
|
||||
target.remove()
|
||||
}
|
||||
}
|
@ -3,12 +3,10 @@
|
||||
// ./bin/rails generate stimulus controllerName
|
||||
|
||||
import { application } from "./application"
|
||||
|
||||
import HelloController from "./hello_controller"
|
||||
application.register("hello", HelloController)
|
||||
|
||||
import PhotoSwipeLightBoxController from "./photo_swipe_lightbox_controller"
|
||||
import FlashMessageController from "./flash_controller"
|
||||
|
||||
console.log("ready to register photo-swipe")
|
||||
application.register("hello", HelloController)
|
||||
application.register("photo-swipe-lightbox", PhotoSwipeLightBoxController)
|
||||
console.log("successful to register photo-swipe")
|
||||
application.register("flash", FlashMessageController)
|
||||
|
@ -1,43 +1,86 @@
|
||||
<h2>Edit <%= resource_name.to_s.humanize %></h2>
|
||||
<div class="min-h-screen container mx-auto px-4 py-8">
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl mb-6">Account Settings</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put, class: "space-y-6" }) do |f| %>
|
||||
<%= render "devise/shared/error_messages", resource: resource %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :email %><br />
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
<div class="form-control">
|
||||
<%= f.label :email, class: "label" %>
|
||||
<%= f.email_field :email,
|
||||
autofocus: true,
|
||||
autocomplete: "email",
|
||||
class: "input input-bordered w-full" %>
|
||||
</div>
|
||||
|
||||
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
|
||||
<div class="alert alert-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<span>Currently waiting confirmation for: <%= resource.unconfirmed_email %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="divider">Change Password</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= f.label :password, class: "label" do %>
|
||||
<span class="label-text">New Password</span>
|
||||
<span class="label-text-alt text-gray-500">(leave blank if you don't want to change it)</span>
|
||||
<% end %>
|
||||
<%= f.password_field :password,
|
||||
autocomplete: "new-password",
|
||||
class: "input input-bordered w-full" %>
|
||||
<% if @minimum_password_length %>
|
||||
<label class="label">
|
||||
<span class="label-text-alt text-gray-500"><%= @minimum_password_length %> characters minimum</span>
|
||||
</label>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= f.label :password_confirmation, "Confirm New Password", class: "label" %>
|
||||
<%= f.password_field :password_confirmation,
|
||||
autocomplete: "new-password",
|
||||
class: "input input-bordered w-full" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control">
|
||||
<%= f.label :current_password, class: "label" do %>
|
||||
<span class="label-text">Current Password</span>
|
||||
<span class="label-text-alt text-gray-500">(required to confirm changes)</span>
|
||||
<% end %>
|
||||
<%= f.password_field :current_password,
|
||||
autocomplete: "current-password",
|
||||
class: "input input-bordered w-full" %>
|
||||
</div>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
<%= f.submit "Update Account", class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="divider">Danger Zone</div>
|
||||
|
||||
<div class="bg-error/10 rounded-box p-4">
|
||||
<h3 class="font-bold text-error mb-2">Delete Account</h3>
|
||||
<p class="text-sm mb-4">Once you delete your account, there is no going back. Please be certain.</p>
|
||||
<%= button_to registration_path(resource_name),
|
||||
class: "btn btn-error",
|
||||
data: {
|
||||
confirm: "Are you sure?",
|
||||
turbo_confirm: "Are you sure you want to delete your account? This action cannot be undone."
|
||||
},
|
||||
method: :delete do %>
|
||||
Delete Account
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-6">
|
||||
<%= link_to "← Back", :back, class: "btn btn-ghost btn-sm" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
|
||||
<div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div>
|
||||
<% end %>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br />
|
||||
<%= f.password_field :password, autocomplete: "new-password" %>
|
||||
<% if @minimum_password_length %>
|
||||
<br />
|
||||
<em><%= @minimum_password_length %> characters minimum</em>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password_confirmation %><br />
|
||||
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br />
|
||||
<%= f.password_field :current_password, autocomplete: "current-password" %>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Update" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h3>Cancel my account</h3>
|
||||
|
||||
<div>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?" }, method: :delete %></div>
|
||||
|
||||
<%= link_to "Back", :back %>
|
||||
</div>
|
@ -1,26 +1,68 @@
|
||||
<h2>Log in</h2>
|
||||
<div class="min-h-screen flex flex-col items-center justify-center px-4">
|
||||
<div class="card w-full max-w-md bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-2xl font-bold text-center mb-6">Sign in to your account</h2>
|
||||
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
|
||||
<div class="field">
|
||||
<%= f.label :email %><br />
|
||||
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
|
||||
</div>
|
||||
<%= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: "space-y-4" }) do |f| %>
|
||||
<div class="form-control">
|
||||
<%= f.label :email, class: "label" %>
|
||||
<%= f.email_field :email,
|
||||
autofocus: true,
|
||||
autocomplete: "email",
|
||||
class: "input input-bordered w-full",
|
||||
placeholder: "your@email.com" %>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<%= f.label :password %><br />
|
||||
<%= f.password_field :password, autocomplete: "current-password" %>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<div class="flex justify-between items-center">
|
||||
<%= f.label :password, class: "label" %>
|
||||
<% if devise_mapping.recoverable? %>
|
||||
<%= link_to "Forgot password?", new_password_path(resource_name), class: "label-text-alt link link-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= f.password_field :password,
|
||||
autocomplete: "current-password",
|
||||
class: "input input-bordered w-full",
|
||||
placeholder: "••••••••" %>
|
||||
</div>
|
||||
|
||||
<% if devise_mapping.rememberable? %>
|
||||
<div class="field">
|
||||
<%= f.check_box :remember_me %>
|
||||
<%= f.label :remember_me %>
|
||||
<% if devise_mapping.rememberable? %>
|
||||
<div class="form-control">
|
||||
<label class="label cursor-pointer justify-start gap-2">
|
||||
<%= f.check_box :remember_me, class: "checkbox checkbox-primary" %>
|
||||
<span class="label-text">Remember me</span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="form-control mt-6">
|
||||
<%= f.submit "Sign in", class: "btn btn-primary w-full" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if devise_mapping.registerable? %>
|
||||
<div class="divider">OR</div>
|
||||
|
||||
<div class="text-center">
|
||||
<p class="text-sm text-base-content/70 mb-4">
|
||||
Don't have an account?
|
||||
<%= link_to "Create an account", new_registration_path(resource_name), class: "link link-primary" %>
|
||||
</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if devise_mapping.omniauthable? %>
|
||||
<div class="space-y-3">
|
||||
<%- resource_class.omniauth_providers.each do |provider| %>
|
||||
<%= button_to omniauth_authorize_path(resource_name, provider),
|
||||
class: "btn btn-outline w-full",
|
||||
data: { turbo: false } do %>
|
||||
<%= image_tag("#{provider}.svg", class: "w-5 h-5 mr-2") if File.exist?(Rails.root.join("app/assets/images/#{provider}.svg")) %>
|
||||
Sign in with <%= OmniAuth::Utils.camelize(provider) %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="actions">
|
||||
<%= f.submit "Log in" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= render "devise/shared/links" %>
|
||||
</div>
|
37
app/views/layouts/_flash_message.html.erb
Normal file
37
app/views/layouts/_flash_message.html.erb
Normal file
@ -0,0 +1,37 @@
|
||||
<div class="fixed top-24 right-4 z-40 w-80 space-y-2" data-controller="flash">
|
||||
<% if notice %>
|
||||
<div class="alert alert-success shadow-lg">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span><%= notice %></span>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-circle" data-action="flash#close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if alert %>
|
||||
<div class="alert alert-error shadow-lg">
|
||||
<div class="flex justify-between w-full">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span><%= alert %></span>
|
||||
</div>
|
||||
<button class="btn btn-ghost btn-sm btn-circle" data-action="flash#close">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
@ -5,9 +5,51 @@
|
||||
Today AI Weather
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="flex-none">
|
||||
<div class="flex-none gap-2">
|
||||
<%= link_to "Cities", cities_path, class: "btn btn-ghost font-sans" %>
|
||||
<%= link_to "Arts", arts_path, class: "btn btn-ghost font-sans" %>
|
||||
|
||||
<% if user_signed_in? %>
|
||||
<div class="dropdown dropdown-end">
|
||||
<label tabindex="0" class="btn btn-ghost">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0012 15.75a7.488 7.488 0 00-5.982 2.975m11.963 0a9 9 0 10-11.963 0m11.963 0A8.966 8.966 0 0112 21a8.966 8.966 0 01-5.982-2.275M15 9.75a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
<span><%= current_user.email %></span>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
||||
</svg>
|
||||
</div>
|
||||
</label>
|
||||
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
|
||||
<li>
|
||||
<%= link_to edit_user_registration_path do %>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
Settings
|
||||
<% end %>
|
||||
</li>
|
||||
<li>
|
||||
<%= button_to destroy_user_session_path, method: :delete, class: "w-full" do %>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75" />
|
||||
</svg>
|
||||
Sign out
|
||||
<% end %>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% else %>
|
||||
<%= link_to new_user_session_path, class: "btn btn-primary" do %>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9" />
|
||||
</svg>
|
||||
Sign in
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -59,25 +59,7 @@
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<main class="pt-16 relative">
|
||||
<div class="toast toast-top toast-end z-[100] fixed pt-20">
|
||||
<% if notice %>
|
||||
<div class="alert alert-success shadow-lg">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<span><%= notice %></span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if alert %>
|
||||
<div class="alert alert-error shadow-lg">
|
||||
<div>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current flex-shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
<span><%= alert %></span>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= render 'layouts/flash_message' %>
|
||||
|
||||
<%= yield %>
|
||||
</main>
|
||||
|
Loading…
Reference in New Issue
Block a user