mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-19 13:19:39 +02:00
Centralize auth messages (#269)
* Add i18n-tasks
* Add auth-related i18n
* Centralize auth messages
* Remove safe navigation
* Revert "Remove safe navigation"
This reverts commit 56b5e01e5e
.
* Remove newline in Gemfile
This commit is contained in:
parent
69698d0463
commit
c5192ee424
17 changed files with 138 additions and 55 deletions
1
Gemfile
1
Gemfile
|
@ -34,6 +34,7 @@ group :development, :test do
|
||||||
gem "rubocop-rails-omakase", require: false
|
gem "rubocop-rails-omakase", require: false
|
||||||
gem "dotenv"
|
gem "dotenv"
|
||||||
gem "letter_opener"
|
gem "letter_opener"
|
||||||
|
gem "i18n-tasks"
|
||||||
end
|
end
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
|
26
Gemfile.lock
26
Gemfile.lock
|
@ -112,6 +112,13 @@ GEM
|
||||||
ast (2.4.2)
|
ast (2.4.2)
|
||||||
base64 (0.2.0)
|
base64 (0.2.0)
|
||||||
bcrypt (3.1.20)
|
bcrypt (3.1.20)
|
||||||
|
better_html (2.0.2)
|
||||||
|
actionview (>= 6.0)
|
||||||
|
activesupport (>= 6.0)
|
||||||
|
ast (~> 2.0)
|
||||||
|
erubi (~> 1.4)
|
||||||
|
parser (>= 2.4)
|
||||||
|
smart_properties
|
||||||
bigdecimal (3.1.6)
|
bigdecimal (3.1.6)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
bootsnap (1.18.3)
|
bootsnap (1.18.3)
|
||||||
|
@ -142,12 +149,24 @@ GEM
|
||||||
ffi (1.16.3)
|
ffi (1.16.3)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
|
highline (3.0.1)
|
||||||
hotwire-livereload (1.3.1)
|
hotwire-livereload (1.3.1)
|
||||||
actioncable (>= 6.0.0)
|
actioncable (>= 6.0.0)
|
||||||
listen (>= 3.0.0)
|
listen (>= 3.0.0)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
i18n (1.14.1)
|
i18n (1.14.1)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
|
i18n-tasks (1.0.13)
|
||||||
|
activesupport (>= 4.0.2)
|
||||||
|
ast (>= 2.1.0)
|
||||||
|
better_html (>= 1.0, < 3.0)
|
||||||
|
erubi
|
||||||
|
highline (>= 2.0.0)
|
||||||
|
i18n
|
||||||
|
parser (>= 3.2.2.1)
|
||||||
|
rails-i18n
|
||||||
|
rainbow (>= 2.2.2, < 4.0)
|
||||||
|
terminal-table (>= 1.5.1)
|
||||||
importmap-rails (2.0.1)
|
importmap-rails (2.0.1)
|
||||||
actionpack (>= 6.0.0)
|
actionpack (>= 6.0.0)
|
||||||
activesupport (>= 6.0.0)
|
activesupport (>= 6.0.0)
|
||||||
|
@ -238,6 +257,9 @@ GEM
|
||||||
rails-html-sanitizer (1.6.0)
|
rails-html-sanitizer (1.6.0)
|
||||||
loofah (~> 2.21)
|
loofah (~> 2.21)
|
||||||
nokogiri (~> 1.14)
|
nokogiri (~> 1.14)
|
||||||
|
rails-i18n (7.0.8)
|
||||||
|
i18n (>= 0.7, < 2)
|
||||||
|
railties (>= 6.0.0, < 8)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.1.0)
|
rake (13.1.0)
|
||||||
rb-fsevent (0.11.2)
|
rb-fsevent (0.11.2)
|
||||||
|
@ -300,6 +322,7 @@ GEM
|
||||||
rexml (~> 3.2, >= 3.2.5)
|
rexml (~> 3.2, >= 3.2.5)
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
|
smart_properties (1.17.0)
|
||||||
sorbet-runtime (0.5.11226)
|
sorbet-runtime (0.5.11226)
|
||||||
stimulus-rails (1.3.3)
|
stimulus-rails (1.3.3)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
@ -316,6 +339,8 @@ GEM
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
tailwindcss-rails (2.3.0-x86_64-linux)
|
tailwindcss-rails (2.3.0-x86_64-linux)
|
||||||
railties (>= 6.0.0)
|
railties (>= 6.0.0)
|
||||||
|
terminal-table (3.0.2)
|
||||||
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
thor (1.3.0)
|
thor (1.3.0)
|
||||||
timeout (0.4.1)
|
timeout (0.4.1)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.6)
|
||||||
|
@ -352,6 +377,7 @@ DEPENDENCIES
|
||||||
debug
|
debug
|
||||||
dotenv
|
dotenv
|
||||||
hotwire-livereload
|
hotwire-livereload
|
||||||
|
i18n-tasks
|
||||||
importmap-rails
|
importmap-rails
|
||||||
inline_svg
|
inline_svg
|
||||||
jbuilder
|
jbuilder
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
class PasswordResetsController < ApplicationController
|
class PasswordResetsController < ApplicationController
|
||||||
layout "auth"
|
layout "auth"
|
||||||
|
|
||||||
|
before_action :set_user_by_token, only: :update
|
||||||
|
|
||||||
def new
|
def new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -12,7 +14,7 @@ class PasswordResetsController < ApplicationController
|
||||||
).password_reset.deliver_later
|
).password_reset.deliver_later
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to root_path, notice: "If an account with that email exists, we have sent a link to reset your password."
|
redirect_to root_path, notice: t(".requested")
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
|
@ -20,7 +22,7 @@ class PasswordResetsController < ApplicationController
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if @user.update(password_params)
|
if @user.update(password_params)
|
||||||
redirect_to new_session_path, notice: "Your password has been reset."
|
redirect_to new_session_path, notice: t(".success")
|
||||||
else
|
else
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
@ -30,7 +32,7 @@ class PasswordResetsController < ApplicationController
|
||||||
|
|
||||||
def set_user_by_token
|
def set_user_by_token
|
||||||
@user = User.find_by_token_for(password_reset: params[:token])
|
@user = User.find_by_token_for(password_reset: params[:token])
|
||||||
redirect_to new_password_reset_path, alert: "Invalid token." unless @user.present?
|
redirect_to new_password_reset_path, alert: t("password_resets.update.invalid_token") unless @user.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def password_params
|
def password_params
|
||||||
|
|
|
@ -6,7 +6,7 @@ class PasswordsController < ApplicationController
|
||||||
|
|
||||||
def update
|
def update
|
||||||
if current_user.update(password_params)
|
if current_user.update(password_params)
|
||||||
redirect_to root_path, notice: "Your password has been updated successfully."
|
redirect_to root_path, notice: t(".success")
|
||||||
else
|
else
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,10 +14,10 @@ class RegistrationsController < ApplicationController
|
||||||
|
|
||||||
if @user.save
|
if @user.save
|
||||||
login @user
|
login @user
|
||||||
flash[:notice] = "You have signed up successfully."
|
flash[:notice] = t(".success")
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
else
|
else
|
||||||
flash[:alert] = "Invalid input, please try again."
|
flash[:alert] = t(".failure")
|
||||||
render :new
|
render :new
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -34,7 +34,7 @@ class RegistrationsController < ApplicationController
|
||||||
|
|
||||||
def claim_invite_code
|
def claim_invite_code
|
||||||
unless InviteCode.claim! params[:user][:invite_code]
|
unless InviteCode.claim! params[:user][:invite_code]
|
||||||
redirect_to new_registration_path, alert: "Invalid invite code, please try again."
|
redirect_to new_registration_path, alert: t("registrations.create.invalid_invite_code")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,13 +9,13 @@ class SessionsController < ApplicationController
|
||||||
login user
|
login user
|
||||||
redirect_to root_path
|
redirect_to root_path
|
||||||
else
|
else
|
||||||
flash.now[:alert] = "Invalid email or password."
|
flash.now[:alert] = t(".invalid_credentials")
|
||||||
render :new, status: :unprocessable_entity
|
render :new, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
logout
|
logout
|
||||||
redirect_to root_path, notice: "You have signed out successfully."
|
redirect_to root_path, notice: t(".logout_successful")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
6
app/helpers/auth_messages_helper.rb
Normal file
6
app/helpers/auth_messages_helper.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module AuthMessagesHelper
|
||||||
|
def auth_messages(form = nil)
|
||||||
|
render "shared/auth_messages", flash: flash,
|
||||||
|
errors: form&.object&.errors&.full_messages || []
|
||||||
|
end
|
||||||
|
end
|
|
@ -18,12 +18,6 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body class="h-full">
|
<body class="h-full">
|
||||||
<% flash.each do |type, msg| %>
|
|
||||||
<div>
|
|
||||||
<%= msg %>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="flex flex-col justify-center min-h-full px-6 py-12">
|
<div class="flex flex-col justify-center min-h-full px-6 py-12">
|
||||||
|
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
<div class="sm:mx-auto sm:w-full sm:max-w-md">
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= form_with url: password_reset_path(token: params[:token]), html: {class: 'space-y-6'} do |form| %>
|
<%= form_with url: password_reset_path(token: params[:token]), html: {class: 'space-y-6'} do |form| %>
|
||||||
|
<%= auth_messages form %>
|
||||||
|
|
||||||
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||||
<%= form.label :password, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
<%= form.label :password, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= form_with url: password_reset_path, html: {class: 'space-y-6'} do |form| %>
|
<%= form_with url: password_reset_path, html: {class: 'space-y-6'} do |form| %>
|
||||||
|
<%= auth_messages form %>
|
||||||
|
|
||||||
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||||
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
||||||
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<h1>Update Password</h1>
|
<h1>Update Password</h1>
|
||||||
|
|
||||||
<%= form_with model: current_user, url: password_path do |form| %>
|
<%= form_with model: current_user, url: password_path do |form| %>
|
||||||
<% if form.object.errors.any? %>
|
<%= auth_messages form %>
|
||||||
<% form.object.errors.full_messages.each do |message| %>
|
|
||||||
<div><%= message %></div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<%= form.label :password_challenge, "Current Password" %>
|
<%= form.label :password_challenge, "Current Password" %>
|
||||||
|
|
|
@ -3,11 +3,7 @@
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= form_with model: @user, url: registration_path, html: {class: 'space-y-6'} do |form| %>
|
<%= form_with model: @user, url: registration_path, html: {class: 'space-y-6'} do |form| %>
|
||||||
<% if form.object.errors.any? %>
|
<%= auth_messages form %>
|
||||||
<% form.object.errors.full_messages.each do |message| %>
|
|
||||||
<div><%= message %></div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||||
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
<%= form.label :email, class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
%>
|
%>
|
||||||
|
|
||||||
<%= form_with url: session_path, html: {class: 'space-y-6'} do |form| %>
|
<%= form_with url: session_path, html: {class: 'space-y-6'} do |form| %>
|
||||||
|
<%= auth_messages form %>
|
||||||
|
|
||||||
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
<div class="relative border border-gray-100 bg-offwhite rounded-xl focus-within:bg-white focus-within:shadow focus-within:opacity-100">
|
||||||
<%= form.label :email, "Email address", class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
<%= form.label :email, "Email address", class: 'p-4 pb-0 block text-sm font-medium text-gray-700' %>
|
||||||
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
<%= form.email_field :email, autofocus: false, autocomplete: "email", required: 'required', placeholder: 'you@example.com', class: 'p-4 pt-1 bg-transparent border-none opacity-50 focus:outline-none focus:ring-0 focus-within:opacity-100 w-full' %>
|
||||||
|
|
7
app/views/shared/_auth_messages.html.erb
Normal file
7
app/views/shared/_auth_messages.html.erb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<% flash.each do |type, msg| %>
|
||||||
|
<div><%= msg %></div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% errors.each do |message| %>
|
||||||
|
<div><%= message %></div>
|
||||||
|
<% end %>
|
25
config/i18n-tasks.yml
Normal file
25
config/i18n-tasks.yml
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
base_locale: en
|
||||||
|
data:
|
||||||
|
read:
|
||||||
|
- config/locales/%{locale}.yml
|
||||||
|
write:
|
||||||
|
- config/locales/%{locale}.yml
|
||||||
|
router: conservative_router
|
||||||
|
search:
|
||||||
|
paths:
|
||||||
|
- app/
|
||||||
|
relative_roots:
|
||||||
|
- app/controllers
|
||||||
|
- app/helpers
|
||||||
|
- app/mailers
|
||||||
|
- app/presenters
|
||||||
|
- app/views
|
||||||
|
strict: true
|
||||||
|
## Files or `File.fnmatch` patterns to exclude from search. Some files are always excluded regardless of this setting:
|
||||||
|
## *.jpg *.jpeg *.png *.gif *.svg *.ico *.eot *.otf *.ttf *.woff *.woff2 *.pdf *.css *.sass *.scss *.less
|
||||||
|
## *.yml *.json *.zip *.tar.gz *.swf *.flv *.mp3 *.wav *.flac *.webm *.mp4 *.ogg *.opus *.webp *.map *.xlsx
|
||||||
|
exclude:
|
||||||
|
- app/assets/images
|
||||||
|
- app/assets/fonts
|
||||||
|
- app/assets/videos
|
||||||
|
- app/assets/builds
|
|
@ -1,31 +1,22 @@
|
||||||
# Files in the config/locales directory are used for internationalization and
|
---
|
||||||
# are automatically loaded by Rails. If you want to use locales other than
|
|
||||||
# English, add the necessary files in this directory.
|
|
||||||
#
|
|
||||||
# To use the locales, use `I18n.t`:
|
|
||||||
#
|
|
||||||
# I18n.t "hello"
|
|
||||||
#
|
|
||||||
# In views, this is aliased to just `t`:
|
|
||||||
#
|
|
||||||
# <%= t("hello") %>
|
|
||||||
#
|
|
||||||
# To use a different locale, set it with `I18n.locale`:
|
|
||||||
#
|
|
||||||
# I18n.locale = :es
|
|
||||||
#
|
|
||||||
# This would use the information in config/locales/es.yml.
|
|
||||||
#
|
|
||||||
# To learn more about the API, please read the Rails Internationalization guide
|
|
||||||
# at https://guides.rubyonrails.org/i18n.html.
|
|
||||||
#
|
|
||||||
# Be aware that YAML interprets the following case-insensitive strings as
|
|
||||||
# booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings
|
|
||||||
# must be quoted to be interpreted as strings. For example:
|
|
||||||
#
|
|
||||||
# en:
|
|
||||||
# "yes": yup
|
|
||||||
# enabled: "ON"
|
|
||||||
|
|
||||||
en:
|
en:
|
||||||
hello: "Hello world"
|
password_resets:
|
||||||
|
create:
|
||||||
|
requested: If an account with that email exists, we have sent a link to reset
|
||||||
|
your password.
|
||||||
|
update:
|
||||||
|
invalid_token: Invalid token.
|
||||||
|
success: Your password has been reset.
|
||||||
|
passwords:
|
||||||
|
update:
|
||||||
|
success: Your password has been updated successfully.
|
||||||
|
registrations:
|
||||||
|
create:
|
||||||
|
failure: Invalid input, please try again.
|
||||||
|
invalid_invite_code: Invalid invite code, please try again.
|
||||||
|
success: You have signed up successfully.
|
||||||
|
sessions:
|
||||||
|
create:
|
||||||
|
invalid_credentials: Invalid email or password.
|
||||||
|
destroy:
|
||||||
|
logout_successful: You have signed out successfully.
|
||||||
|
|
34
test/i18n_test.rb
Normal file
34
test/i18n_test.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
require "i18n/tasks"
|
||||||
|
|
||||||
|
class I18nTest < ActiveSupport::TestCase
|
||||||
|
def setup
|
||||||
|
@i18n = I18n::Tasks::BaseTask.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_missing_keys
|
||||||
|
missing_keys = @i18n.missing_keys
|
||||||
|
assert_empty missing_keys,
|
||||||
|
"Missing #{missing_keys.leaves.count} i18n keys, run `i18n-tasks missing' to show them"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_unused_keys
|
||||||
|
unused_keys = @i18n.unused_keys
|
||||||
|
assert_empty unused_keys,
|
||||||
|
"#{unused_keys.leaves.count} unused i18n keys, run `i18n-tasks unused' to show them"
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_files_are_normalized
|
||||||
|
non_normalized = @i18n.non_normalized_paths
|
||||||
|
error_message = "The following files need to be normalized:\n" \
|
||||||
|
"#{non_normalized.map { |path| " #{path}" }.join("\n")}\n" \
|
||||||
|
"Please run `i18n-tasks normalize' to fix"
|
||||||
|
assert_empty non_normalized, error_message
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_no_inconsistent_interpolations
|
||||||
|
inconsistent_interpolations = @i18n.inconsistent_interpolations
|
||||||
|
error_message = "#{inconsistent_interpolations.leaves.count} i18n keys have inconsistent interpolations.\n" \
|
||||||
|
"Please run `i18n-tasks check-consistent-interpolations' to show them"
|
||||||
|
assert_empty inconsistent_interpolations, error_message
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue