feat: enhance devise views with new styling

- Updated confirmation, password, unlock, and session views to use a
  card-based layout for improved visual appeal.
- Added responsive design features to ensure compatibility across devices.
- Enhanced usability by providing clear placeholders in form fields.

These changes improve the user experience during account recovery and
management processes. The design promotes a modern interface while
maintaining functionality.
This commit is contained in:
songtianlun 2025-02-11 16:43:05 +08:00
parent 7d25bc8f11
commit be1f76a76d
9 changed files with 153 additions and 92 deletions

View File

@ -1,16 +1,36 @@
<h2>Resend confirmation instructions</h2>
<div class="min-h-screen flex 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 mb-4">Resend confirmation instructions</h2>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<p class="text-sm text-gray-600 mb-4">
Didn't receive confirmation instructions? Enter your email below to resend.
</p>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-control">
<%= f.label :email, class: "label" %>
<%= f.email_field :email,
autofocus: true,
autocomplete: "email",
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email),
class: "input input-bordered w-full",
placeholder: "Enter your email" %>
</div>
<div class="form-control mt-6">
<%= f.submit "Resend confirmation instructions",
class: "btn btn-primary w-full" %>
</div>
<% end %>
<div class="divider"></div>
<div class="text-sm text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
<div class="actions">
<%= f.submit "Resend confirmation instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>

View File

@ -1,25 +1,39 @@
<h2>Change your password</h2>
<div class="min-h-screen flex 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 mb-4">Change your password</h2>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= f.hidden_field :reset_password_token %>
<div class="field">
<%= f.label :password, "New password" %><br />
<% if @minimum_password_length %>
<em>(<%= @minimum_password_length %> characters minimum)</em><br />
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password" %>
<div class="form-control">
<%= f.label :password, "New password", class: "label" %>
<% if @minimum_password_length %>
<small class="text-gray-500">(<%= @minimum_password_length %> characters minimum)</small>
<% end %>
<%= f.password_field :password, autofocus: true, autocomplete: "new-password",
class: "input input-bordered w-full",
placeholder: "Enter new password" %>
</div>
<div class="form-control mt-4">
<%= f.label :password_confirmation, "Confirm new password", class: "label" %>
<%= f.password_field :password_confirmation, autocomplete: "new-password",
class: "input input-bordered w-full",
placeholder: "Confirm new password" %>
</div>
<div class="form-control mt-6">
<%= f.submit "Change my password", class: "btn btn-primary w-full" %>
</div>
<% end %>
<div class="divider"></div>
<div class="text-sm text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
<div class="field">
<%= f.label :password_confirmation, "Confirm new password" %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password" %>
</div>
<div class="actions">
<%= f.submit "Change my password" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>

View File

@ -1,16 +1,29 @@
<h2>Forgot your password?</h2>
<div class="min-h-screen flex 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 mb-4">Forgot your password?</h2>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) 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",
placeholder: "Enter your email" %>
</div>
<div class="form-control mt-6">
<%= f.submit "Send me reset password instructions",
class: "btn btn-primary w-full" %>
</div>
<% end %>
<div class="divider"></div>
<div class="text-sm text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
<div class="actions">
<%= f.submit "Send me reset password instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>

View File

@ -43,26 +43,11 @@
<% 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 class="text-center text-sm">
<%= render "devise/shared/links" %>
</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>
</div>
</div>

View File

@ -1,32 +1,32 @@
<div class="space-y-4">
<%- if controller_name != 'sessions' %>
<%= link_to "Sign in", new_session_path(resource_name), class: "link link-primary" %>
<p><%= link_to "Sign in", new_session_path(resource_name), class: "link link-primary" %></p>
<% end %>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %>
<%= link_to "Create an account", new_registration_path(resource_name), class: "link link-primary" %>
<p><%= link_to "Create an account", new_registration_path(resource_name), class: "link link-primary" %></p>
<% end %>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
<%= link_to "Forgot your password?", new_password_path(resource_name), class: "link link-primary" %>
<p><%= link_to "Forgot your password?", new_password_path(resource_name), class: "link link-primary" %></p>
<% end %>
<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
<%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "link link-secondary text-sm" %>
<p><%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: "link link-secondary text-sm" %></p>
<% end %>
<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
<%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: "link link-secondary text-sm" %>
<p><%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name), class: "link link-secondary text-sm" %></p>
<% end %>
<%- if devise_mapping.omniauthable? %>
<div class="space-y-2">
<%- resource_class.omniauth_providers.each do |provider| %>
<%= button_to omniauth_authorize_path(resource_name, provider),
<p><%= button_to omniauth_authorize_path(resource_name, provider),
class: "btn btn-outline w-full gap-2",
data: { turbo: false } do %>
<%= image_tag("#{provider}.svg", class: "w-5 h-5") if File.exist?(Rails.root.join("app/assets/images/#{provider}.svg")) %>
Sign in with <%= OmniAuth::Utils.camelize(provider) %>
Sign in with <%= OmniAuth::Utils.camelize(provider) %></p>
<% end %>
<% end %>
</div>

View File

@ -1,16 +1,34 @@
<h2>Resend unlock instructions</h2>
<div class="min-h-screen flex 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 mb-4">Unlock Account</h2>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<p class="text-sm text-gray-600 mb-4">
Your account has been locked due to too many failed login attempts.
Enter your email to receive unlock instructions.
</p>
<div class="field">
<%= f.label :email %><br />
<%= f.email_field :email, autofocus: true, autocomplete: "email" %>
<%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-control">
<%= f.label :email, class: "label" %>
<%= f.email_field :email, autofocus: true, autocomplete: "email",
class: "input input-bordered w-full",
placeholder: "Enter your email" %>
</div>
<div class="form-control mt-6">
<%= f.submit "Resend unlock instructions",
class: "btn btn-primary w-full" %>
</div>
<% end %>
<div class="divider"></div>
<div class="text-sm text-center">
<%= render "devise/shared/links" %>
</div>
</div>
</div>
<div class="actions">
<%= f.submit "Resend unlock instructions" %>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>

View File

@ -1 +1 @@
eq4Y6iazLmvUUO+x38IQUs2iC/Ti6GXAmFGcUSIxTxqhENoqTGSWfVCu34gV7pZAnwsvc6T/csaUYutI2SaY1bS692Dm5WAWPzvAXkR5aJtmRtt8v9GMuSxcMLLkpxPGQzTWO+lVnh6ecFAQDkh2takeSSlE5OW6i4kNwqouRmvfTRVkIhmOHDJR/pRkQbk6oPYNwcx477m7KPcX1gN2k4Et5id2liFlXE7k3x4Xli9SM4dWjIV67E7wabC5+0vbz5KmvuyBqt2URW8klhQheGecOlYV37XmO0lErV1zBP2lWB9pz9QqsHNe71EMvsPwhYtYVIEyDt322pEdT3FUFOa4XBGxu2MGQFb+8Ff3tCi/DYj/kJec37sP/zcHb59AgxCUJB0VENQqiaOu2u0NTCgjODbaYnoJws8IaNybGEaEzmJkGefFG03nEEpdSpxD5QPU4MJC+byihwkA8iGzO1bW4iW+oiIPgn4RHX7UR4ADx3jGTlDsAX1pQIvzZCnf2/eRTlgcfz+QXzbvQ7wBOIHGG6kMa6rhVwYf1s9RoD7+HlYNN/egcCqj9QshibM7JADdwAQebn3An+/8gmYmRKgxQtwBf/8XSMT0m2oSfldiS3D6+kRrL4tUNjSIHqWUSQPvZomAeZrH4QtuK5dKov9OSqoSwlMUBBc9N+GvFxKIpJDOKF8JWNKoQUv+5CUWoU+AndlJBAoBnvwJbdNC2w1ergaHoVKfdDA4NU6U/TAtEbwpSbnrtYTf8Mxb/7rX30Ck1tQEuxQHUhiRozFLe4qsOhI2fQ0VOJ1HEfxK0ttdmo8i5FnpQ8iqjFE0LsAhIe5fFYqOqIpRbu/Uyt3fgThUD9p3QqBVX0ZPwNVnlqQwYfpRnrv3VNKzn0h6slyd2/hZYRVr+S2VGqxJgE0AZqTLZLSojeXHGrA3kHT1m0ncaOKEJ0YnGF9mKcbm43ViC6wgPBudbqs6mF0RTfDsAyK0JcWA4f4ssVH7QvjZgqrXz5AD4mth7U9gOSQSLQ9wxbiZTY8Csa1ZGNx3oszscFQ5Tw85tpSkk/Fuv6/7QKcVy/uFEyhWGVeJwWrLXsAyYP1lwbwyixgWflXE6APBopeVVVB7fLpEZTHyUyo/MHLe4Lp2Yw1cd1K8HOi0nIdSnEbc3HlIWwEsleZ4APFonvw7/JPlsceWUqgqbtW2KP9EtW6abplSwC9z8AuF6iFkEGYD1LFp5wE2gboaLtfUyFr0Xw1LVH3hn15ku3p7ptihvMqnHk05ulNhBAk6MVS231koMb0xIRTaP7BaUu25i0rmJgerWw2BJdjsiBab32s5u7sWnWR8fMCD55tsFMyiEZQHp1InXUVx8GBtbE9K0dTAvcIS0mKH8+DY61R9HeJ46L7AX6BADOpug9ScjiE62owl7CR95M97V+B53muKVSXT9tnqsYYJqP3fVk8X264i97T2JxOKwAxPxoPljv9Y6nwH8ZlR7s1sPCviOQxZBssEkmTxiYjX9J0+S0T/q9M6NwYZn1weEjCPFZbwHltcXBTqPuvR0iUN+itUBTnDkS0F3CkF12JmfYWJSOAh3PR+sgEcrJszySxoGMEv3smUx7MgJ+eZWIkbXbx0JWzEf/UJIparRGYVP4GCerZ7EY6Q2HG4WkyR3v2A7Ityzv0jI/u3DDXoEIB/+tW7xaeSzTJjaLjO--wzTRxzkF39xx+3YB--yfEzFDCIT3kpIEDGL5skrQ==
WIRCFJoA1YUpdSuljSkBqEJvBpftvTRGA73T9Oy9vXNnOIHcnZDHtlhh2VuF4Xtq41d0aPJ2qPI4kUeGzM34I6FITsSY6i1y5ZEW4vUa8F/BRTO0/viXBPiSAS+BkWVEWYrFkNWJAIitVLG0hq8vCBEEs/KctxY8w9h0LwgyRV2udPzn6A7wyoJhR/V5nRamU6USFiHh6cpTlFQdSIZ0sC6v21IIS+G/ZPJXNNTw2+gVaDfL1yuX1nrLsxeMsb30iugksDj/gCVi42kF/hPVOGIZCM1ftM5j4Q+BcTDyEVqIMeXRdtegLKWM5sI0yB070m7gMRK1Oewa3z+NuPLGp1Stuuo977LGuATmP3/GnAMEZe8tgMfeKYeQv2+TerpLmO07KayOzN3j0qSs/OqrcSUP+SByxRBmpmeXNEQ6q+f1ZDreo/Q1Cm+aZe1UWXYpcgd6MVGHjYUna4SgTQqUKHKdzvf7Yx8fOjrzHRqZ6Y8LWq53Vzr8oNJ9IoNAh9TSJMF4tR3M+OQ7+SARgwQLoVsehgK1Z658GMvhVoeLrPTwvdID54+TSFMepMnownDJPZGKZoZK1+NlUyzz3rJ2iH9AxZnvwPMCcmlHH/Fh/FANmtQxd3DIPpjHHyZjxFLsR9tNuIapXPK8SiRhWbtYo+4i0/dgUvD8SfECSOklfx2tVIhvxTjg4AM+WtI9gieDISq+AZe9fjqyv8vLOJuDX8Nk1k69DKIqB2u+OFJW2/PPYFz2Ry283ep+VRKwS2qlsFEkTRkzPJ5y9dVrCXIYl0R7vgC+GDrUkK1IffZmTaJ+rqmzzwLF1OwPdtN/QuJm0aZZQmN9pQgUjt1PEgthQVW9dt/ElDZKgVjIcds3F1kX0+ZWJQRvBldilC9W5lRSea/9x253dw3wj1ERF6sZsIJCwcxTJigTM4rEfcjrMnEIfiW1HWc1LTJ5DUIh5GISBv7NDF52voP2TBamAq2Zg2vVg2v+bU9ydkkIVOm4oLYZWDQyDBeQC4KJscFCSo1ZxvRuqIRtnI3h2KoXJeK63cxGcVxbHcyYO5xTPXmPkk18pFPYoXe46an6xTEzxYXQMfWuIUvM9oOjgZXFvdgPwDWzL/pSZb6qW5MiDqvlt38ddivUtsZLMMtpgZjXOAyKQyIQNSOHyrrYoj6r8PdFiCZMWjn1B0HmXgIl47jrb9o1nrif9rb5DSUY2Pvl0+TeRnUB8r7Wa+t+UdHjrq6PEaXVOQbej78Rzo5xmZ5XknlbKv02Vjpcm8EIkc00nKvMo29f0mr1Wv48CL+tuDxiW3v2yX8NcTnMEoQkTTaw5wJmSb3dmPdVZwjcCdbqs1CbRpc/ngX7nJP9kF2P57BWqqYq6V4fWk3vEgLpTUsTCbJvTlCx7McLKy2/smtz2HQl6Vs5jofpSIXgNC35TBkPP22XLFiBTah0E2IUeF2TyuLphswQsMDzKzADMxkKpzEVNLCzzKfygDfsfsJqZjKBGHJz3RHDbGoqmgP/4u7FEBV9gUXFt5i5k71HxSnafbpcFkLk3AdfNLso2qUNZSj/8EzY33WFjQIEAFJeTqB8WML/LcVvNqQqvjUyUMgvA+rZq+BwS8svqJEvcRFmZhjeqQbsYs+C+jOlwjrohoak/CgfiHofuXWvnNyi0+PT6bPO5CsjbpJLwFxbPi19tWmnMR7ZOqIE3vN4bf8Rjg4T5zhKKbgIU7wEP+i0u9p0zPFEa6EZMxp8/5q50EjXdlY/cHa/Sp05x2h8jhi1Trxgg8yFOdu2P+dStAwW3Sv4x2o=--aZKHys4Q3dCV2P+L--yEOdlrHAYleGIYmdKAB4FQ==

View File

@ -59,7 +59,18 @@ Rails.application.configure do
# config.action_mailer.raise_delivery_errors = false
# Set host to be used by links generated in mailer templates.
config.action_mailer.default_url_options = { host: "todayaiweather.com" }
config.action_mailer.default_url_options = { host: ENV.fetch("RAILS_BASE_URL", "todayaiweather.com") }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: "smtpdm.aliyun.com",
port: 465,
user_name: "noreply@mail.frytea.com",
password: Rails.application.credentials.smtp.password,
ssl: true,
authentication: "plain",
enable_starttls_auto: true,
open_timeout: 5,
read_timeout: 5 }
# Specify outgoing SMTP server. Remember to add smtp/* credentials via rails credentials:edit.
# config.action_mailer.smtp_settings = {

View File

@ -194,24 +194,24 @@ Devise.setup do |config|
# Defines which strategy will be used to lock an account.
# :failed_attempts = Locks an account after a number of failed attempts to sign in.
# :none = No lock strategy. You should handle locking by yourself.
# config.lock_strategy = :failed_attempts
config.lock_strategy = :failed_attempts
# Defines which key will be used when locking and unlocking an account
# config.unlock_keys = [:email]
config.unlock_keys = [:email]
# Defines which strategy will be used to unlock an account.
# :email = Sends an unlock link to the user email
# :time = Re-enables login after a certain amount of time (see :unlock_in below)
# :both = Enables both strategies
# :none = No unlock strategy. You should handle unlocking by yourself.
# config.unlock_strategy = :both
config.unlock_strategy = :both
# Number of authentication tries before locking an account if lock_strategy
# is failed attempts.
# config.maximum_attempts = 20
config.maximum_attempts = 5
# Time interval to unlock the account if :time is enabled as unlock_strategy.
# config.unlock_in = 1.hour
config.unlock_in = 24.hour
# Warn on the last attempt before the account is locked.
# config.last_attempt_warning = true