Fix missing error messages on user registration and other forms

This commit is contained in:
Eugene Burmakin 2025-11-07 11:08:57 +01:00
parent 888e48ccf2
commit 2f160b8d97
7 changed files with 103 additions and 18 deletions

View file

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## Fixed ## Fixed
- Taiwan flag is now shown on its own instead of in combination with China flag. - Taiwan flag is now shown on its own instead of in combination with China flag.
- On the registration page and other user forms, if something goes wrong, error messages are now shown to the user.
## Changed ## Changed

View file

@ -51,7 +51,7 @@ class Users::RegistrationsController < Devise::RegistrationsController
end end
def set_invitation def set_invitation
return unless invitation_token.present? return if invitation_token.blank?
@invitation = Family::Invitation.find_by(token: invitation_token) @invitation = Family::Invitation.find_by(token: invitation_token)
end end

View file

@ -17,6 +17,8 @@
</div> </div>
<div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5"> <div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5">
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), class: 'form-body', method: :put, data: { turbo_method: :put, turbo: false }) do |f| %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), class: 'form-body', method: :put, data: { turbo_method: :put, turbo: false }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<div class="form-control"> <div class="form-control">
<%= f.label :email, class: 'label' do %> <%= f.label :email, class: 'label' do %>
<span class="label-text">Email</span> <span class="label-text">Email</span>

View file

@ -16,12 +16,23 @@
</span> </span>
</div> </div>
<% else %> <% else %>
<h1 class="text-5xl font-bold text-base-content">Register now!</h1> <h1 class="text-5xl font-bold text-base-content">Almost there!</h1>
<p class="py-6 text-base-content opacity-70">and take control over your location data.</p>
<% end %> <% end %>
<p class="py-6 text-base-content opacity-70">
Only a few steps left until you get control over your location data!
</p>
<ol>
<li class="mb-2">1. Create your account</li>
<li class="mb-2">2. Configure your mobile app</li>
<li class="mb-2">3. Start tracking your location data securely</li>
<li class="mb-2">4. ...</li>
<li class="mb-2">5. You're beautiful!</li>
</ol>
</div> </div>
<div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5"> <div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5">
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), class: 'form-body', html: { data: { turbo: session[:dawarich_client] == 'ios' ? false : true } }) do |f| %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), class: 'form-body', html: { data: { turbo: session[:dawarich_client] == 'ios' ? false : true } }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<% if @invitation %> <% if @invitation %>
<%= f.hidden_field :invitation_token, value: params[:invitation_token] %> <%= f.hidden_field :invitation_token, value: params[:invitation_token] %>
<% end %> <% end %>
@ -32,7 +43,7 @@
<% end %> <% end %>
<%= f.email_field :email, autofocus: true, autocomplete: "email", <%= f.email_field :email, autofocus: true, autocomplete: "email",
readonly: @invitation.present?, readonly: @invitation.present?,
class: "input input-bordered #{@invitation ? 'input-disabled' : ''}" %> class: "input input-bordered w-full #{@invitation ? 'input-disabled' : ''}" %>
</div> </div>
<div class="form-control"> <div class="form-control">
@ -42,7 +53,7 @@
<% if @minimum_password_length %> <% if @minimum_password_length %>
<em class="text-base-content opacity-60 text-sm">(<%= @minimum_password_length %> characters minimum)</em> <em class="text-base-content opacity-60 text-sm">(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br /> <% end %><br />
<%= f.password_field :password, autocomplete: "new-password", class: 'input input-bordered' %> <%= f.password_field :password, autocomplete: "new-password", class: 'input input-bordered w-full' %>
</div> </div>
<div class="form-control"> <div class="form-control">
@ -52,7 +63,7 @@
<% if @minimum_password_length %> <% if @minimum_password_length %>
<em class="text-base-content opacity-60 text-sm">(<%= @minimum_password_length %> characters minimum)</em> <em class="text-base-content opacity-60 text-sm">(<%= @minimum_password_length %> characters minimum)</em>
<% end %><br /> <% end %><br />
<%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'input input-bordered' %> <%= f.password_field :password_confirmation, autocomplete: "new-password", class: 'input input-bordered w-full' %>
</div> </div>
<% if !DawarichSettings.self_hosted? %> <% if !DawarichSettings.self_hosted? %>

View file

@ -20,6 +20,8 @@
</div> </div>
<div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5"> <div class="card flex-shrink-0 w-full max-w-sm shadow-2xl bg-base-100 px-5 py-5">
<%= form_for(resource, as: resource_name, url: session_path(resource_name), class: 'form-body', html: { data: { turbo: session[:dawarich_client] == 'ios' ? false : true } }) do |f| %> <%= form_for(resource, as: resource_name, url: session_path(resource_name), class: 'form-body', html: { data: { turbo: session[:dawarich_client] == 'ios' ? false : true } }) do |f| %>
<%= render "devise/shared/error_messages", resource: resource %>
<% if @invitation %> <% if @invitation %>
<%= hidden_field_tag :invitation_token, params[:invitation_token] %> <%= hidden_field_tag :invitation_token, params[:invitation_token] %>
<% end %> <% end %>

View file

@ -1,15 +1,20 @@
<% if resource.errors.any? %> <% if resource.errors.any? %>
<div id="error_explanation" data-turbo-cache="false"> <div id="error_explanation" class="alert alert-error mb-4" data-turbo-cache="false">
<h2> <%= icon 'circle-x' %>
<%= I18n.t("errors.messages.not_saved", <div class="font-bold mb-4 flex items-center gap-2">
count: resource.errors.count, <div>
resource: resource.class.model_name.human.downcase) <h3 class="font-bold">
%> <%= I18n.t("errors.messages.not_saved",
</h2> count: resource.errors.count,
<ul> resource: resource.class.model_name.human.downcase)
<% resource.errors.full_messages.each do |message| %> %>
<li><%= message %></li> </h3>
<% end %> <ul class="text-sm mt-1">
</ul> <% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
</div>
</div> </div>
<% end %> <% end %>

View file

@ -326,6 +326,70 @@ RSpec.describe 'Users::Registrations', type: :request do
end end
end end
describe 'Validation Error Handling' do
context 'when trying to register with an existing email' do
let!(:existing_user) { create(:user, email: 'existing@example.com') }
it 'renders the registration form with error message' do
post user_registration_path, params: {
user: {
email: existing_user.email,
password: 'password123',
password_confirmation: 'password123'
}
}
expect(response).to have_http_status(:unprocessable_content)
expect(response.body).to include('Email has already been taken')
expect(response.body).to include('error_explanation')
end
it 'does not create a new user' do
expect do
post user_registration_path, params: {
user: {
email: existing_user.email,
password: 'password123',
password_confirmation: 'password123'
}
}
end.not_to change(User, :count)
end
end
context 'when password is too short' do
it 'renders the registration form with error message' do
post user_registration_path, params: {
user: {
email: 'newuser@example.com',
password: 'short',
password_confirmation: 'short'
}
}
expect(response).to have_http_status(:unprocessable_content)
expect(response.body).to include('Password is too short')
expect(response.body).to include('error_explanation')
end
end
context 'when passwords do not match' do
it 'renders the registration form with error message' do
post user_registration_path, params: {
user: {
email: 'newuser@example.com',
password: 'password123',
password_confirmation: 'different123'
}
}
expect(response).to have_http_status(:unprocessable_content)
expect(response.body).to include("Password confirmation doesn")
expect(response.body).to include('error_explanation')
end
end
end
describe 'UTM Parameter Tracking' do describe 'UTM Parameter Tracking' do
let(:utm_params) do let(:utm_params) do
{ {