diff --git a/app/frontend/stylesheets/application.tailwind.css b/app/frontend/stylesheets/application.tailwind.css index 98775a72c..5b22ceaac 100644 --- a/app/frontend/stylesheets/application.tailwind.css +++ b/app/frontend/stylesheets/application.tailwind.css @@ -134,10 +134,16 @@ position: relative; } +.password-field { + -webkit-appearance: none; + appearance: none; +} + .toggle-password { position: absolute; right: 12px; - top: 36px; /* aligns with input vertically */ + top: 50%; + transform: translateY(-50%); background: none; border: none; padding: 0; @@ -151,7 +157,10 @@ /* Hide Chrome / Edge / Safari (macOS + iOS) password reveal icon */ input[type="password"]::-webkit-textfield-decoration-container { - display: none; + visibility: hidden; + pointer-events: none; + width: 0; + height: 0; } input[type="password"]::-webkit-credentials-auto-fill-button { @@ -160,6 +169,12 @@ input[type="password"]::-webkit-credentials-auto-fill-button { pointer-events: none; } +<<<<<<< HEAD +/* Safari fix: Ensure password field has proper height */ +.password-field { + height: 2.5rem; /* Matches Tailwind's default input height with p-2 */ +} + /* Truncated comment textareas - compact at rest, expand on hover/focus */ textarea.comment-truncated { overflow: hidden; diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb index da49bdfd1..1073a31e8 100644 --- a/app/views/devise/passwords/edit.html.erb +++ b/app/views/devise/passwords/edit.html.erb @@ -12,50 +12,42 @@ <%= f.hidden_field :reset_password_token %> -
- <%= f.input :password, - as: :password, - label: "New password", - required: true, - autofocus: true, - autocomplete: "new-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - input_html: { - class: "form-control form-input password-field", - data: { password_toggle_target: "input" } - } %> - +
+ +
+ <%= f.password_field :password, + class: "w-full form-control form-input password-field pr-10", + required: true, + minlength: 5, + autofocus: true, + autocomplete: "new-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +
+

Password must be at least 5 characters long

-
- <%= f.input :password_confirmation, - as: :password, - label: "Confirm new password", - required: true, - autocomplete: "new-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - input_html: { - class: "form-control form-input password-field", - data: { password_toggle_target: "input" } - } %> - +
+ +
+ <%= f.password_field :password_confirmation, + class: "w-full form-control form-input password-field pr-10", + required: true, + autocomplete: "new-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +
diff --git a/app/views/devise/sessions/new.html.erb b/app/views/devise/sessions/new.html.erb index 93e8fc213..e8c2ff43b 100644 --- a/app/views/devise/sessions/new.html.erb +++ b/app/views/devise/sessions/new.html.erb @@ -16,29 +16,26 @@ autofocus: true, required: true, input_html: { class: "w-full rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 shadow-sm" } %> -
- <%= f.input :password, - as: :password, - required: true, - autocomplete: "current-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - input_html: { - class: "form-control password-field rounded-md border-gray-300 shadow-sm - focus:ring-blue-500 focus:border-blue-500 p-2 pr-10", - data: { password_toggle_target: "input" } - } %> - - +
+ +
+ <%= f.password_field :password, + class: "w-full form-control password-field rounded-md border-gray-300 shadow-sm + focus:ring-blue-500 focus:border-blue-500 px-3 py-2 pr-10", + required: true, + autocomplete: "current-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +
<% if devise_mapping.rememberable? %> diff --git a/app/views/users/change_password.html.erb b/app/views/users/change_password.html.erb index 8cd1e2661..a0fef082e 100644 --- a/app/views/users/change_password.html.erb +++ b/app/views/users/change_password.html.erb @@ -7,26 +7,21 @@ method: :post, html: { class: "space-y-4" } do |f| %> -
- <%= f.input :current_password, - as: :password, - required: true, - autocomplete: "current-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - input_html: { - class: "password-field rounded-md border-gray-300 shadow-sm p-2 pr-10", - data: { password_toggle_target: "input" } - } %> - +
+ +
+ <%= f.password_field :current_password, + class: "w-full password-field rounded-md border-gray-300 shadow-sm p-2 pr-10", + required: true, + autocomplete: "current-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +

Don't remember your password? @@ -39,56 +34,40 @@ } %>

-
-
- <%= f.input :password, - as: :password, - label: "New password", +
+ +
+ <%= f.password_field :password, + id: "change-password-new-password", + class: "w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 pr-10", required: true, autocomplete: "new-password", autocorrect: "off", autocapitalize: "off", spellcheck: "false", - input_html: { - id: "change-password-new-password", - class: "rounded-md border-gray-300 shadow-sm - focus:ring-blue-500 focus:border-blue-500 - p-2 pr-10 w-full", - data: { password_toggle_target: "input" } - } %> -
-
- <%= f.input :password_confirmation, - as: :password, - label: "New password confirmation", - autocomplete: "new-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - required: true, - input_html: { +
+ +
+ <%= f.password_field :password_confirmation, id: "change-password-new-password-confirmation", - class: "rounded-md border-gray-300 shadow-sm - focus:ring-blue-500 focus:border-blue-500 - p-2 pr-10", - data: { password_toggle_target: "input" } - } %> - + class: "w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 pr-10", + required: true, + autocomplete: "new-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +
<%= f.submit "Change Password", class: "btn btn-primary" %> diff --git a/app/views/welcome/show.html.erb b/app/views/welcome/show.html.erb index 3361857a9..ea424bebb 100644 --- a/app/views/welcome/show.html.erb +++ b/app/views/welcome/show.html.erb @@ -19,59 +19,41 @@ <% end %> -
-
- <%= f.input :password, - as: :password, - label: "New password", +
+ +
+ <%= f.password_field :password, + id: "change-password-new-password", + class: "w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 pr-10", required: true, autocomplete: "new-password", autocorrect: "off", autocapitalize: "off", spellcheck: "false", - input_html: { - id: "change-password-new-password", - class: "rounded-md border-gray-300 shadow-sm - focus:ring-blue-500 focus:border-blue-500 - p-2 pr-10 w-full", - data: { password_toggle_target: "input" } - } %> - -
-
- <%= f.input :password_confirmation, - as: :password, - label: "New password confirmation", - autocomplete: "new-password", - autocorrect: "off", - autocapitalize: "off", - spellcheck: "false", - required: true, - input_html: { +
+ +
+ <%= f.password_field :password_confirmation, id: "change-password-new-password-confirmation", - class: "rounded-md border-gray-300 shadow-sm - focus:ring-blue-500 focus:border-blue-500 - p-2 pr-10", - data: { password_toggle_target: "input" } - } %> - - + class: "w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500 focus:border-blue-500 p-2 pr-10", + required: true, + autocomplete: "new-password", + autocorrect: "off", + autocapitalize: "off", + spellcheck: "false", + data: { password_toggle_target: "input" } %> + +
<% if User.devise_modules.include?(:rememberable) %> diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 86bb93020..126b3bbcf 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -58,5 +58,5 @@ en: not_found: "not found" not_locked: "was not locked" not_saved: - one: "1 error prohibited this %{resource} from being saved:" - other: "%{count} errors prohibited this %{resource} from being saved:" + one: "1 error prevented this %{resource} from being saved:" + other: "%{count} errors prevented this %{resource} from being saved:" diff --git a/spec/views/devise/passwords/edit.html.erb_spec.rb b/spec/views/devise/passwords/edit.html.erb_spec.rb new file mode 100644 index 000000000..256e8cc54 --- /dev/null +++ b/spec/views/devise/passwords/edit.html.erb_spec.rb @@ -0,0 +1,55 @@ +require 'rails_helper' + +RSpec.describe "devise/passwords/edit", type: :view do + let(:user) { create(:user) } + + before do + user.reset_password_token = Devise.friendly_token + assign(:resource, user) + assign(:resource_name, :user) + u = user + mapping = Devise.mappings[:user] + view.define_singleton_method(:resource) { u } + view.define_singleton_method(:resource_name) { :user } + view.define_singleton_method(:devise_mapping) { mapping } + render + end + + it "displays new password field" do + expect(rendered).to have_field("New password", type: :password) + end + + it "displays password confirmation field" do + expect(rendered).to have_field("Confirm new password", type: :password) + end + + it "has a submit button" do + expect(rendered).to have_button("Set password") + end + + it "displays password requirements hint" do + expect(rendered).to have_content("Password must be at least 5 characters long") + end + + it "has minlength attribute on password field" do + expect(rendered).to have_css('input[type="password"][minlength="5"]') + end + + context "when user has password errors" do + before do + user.errors.add(:password, "is too short (minimum is 5 characters)") + user.errors.add(:password_confirmation, "doesn't match Password") + render + end + + it "displays error explanation container" do + expect(rendered).to have_css('div#error_explanation') + end + + it "displays error messages" do + expect(rendered).to have_content("prevented this user from being saved") + expect(rendered).to have_content("Password is too short") + expect(rendered).to have_content("Password confirmation doesn't match") + end + end +end