diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 06ee8a0..339400e 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -15,7 +15,13 @@ *= require_self */ -/* Strive for simplicity. Style elements only, if possible. */ +/* Strive for simplicity: + * * style elements/tags only - if possible, + * * replace element/tag name with class name - if element has to be styled + * differently depending on context (e.g. form) + * + * NOTE: Style in a modular way, similar to how CSS @scope would be used, + * to make transition easier once @scope is widely available */ :root { --color-focus-gray: #f3f3f3; --color-border-gray: #dddddd; @@ -215,7 +221,7 @@ body { font-family: system-ui; margin: 0.4em; } -body:not(:has(.topside)) { +body:not(:has(.topside-area)) { grid-template-areas: "header header header" "nav nav nav" @@ -250,16 +256,16 @@ header { fill: var(--color-blue); } -.topside { +.topside-area { grid-area: topside; } -.leftside { +.leftside-area { grid-area: leftside; } -.main { +.main-area { grid-area: main; } -.rightside { +.rightside-area { grid-area: rightside; } @@ -270,7 +276,7 @@ header { grid-template-columns: auto 1fr auto; grid-template-rows: max-content; } -.tools { +.tools-area { grid-area: tools; } @@ -334,46 +340,43 @@ header { opacity: 1; } - -/* TODO: Update form styling: simplify selectors, deduplicate, remove non-font rem. */ -form table { - border-spacing: 0.8rem; +/* TODO: Hover over invalid should work like in measurements (thin vs thick border) */ +.labeled-form { + align-items: center; + display: grid; + gap: 0.9em 1.1em; + grid-template-columns: 1fr minmax(max-content, 0.5fr) 1fr; } -form tr td:first-child { +.labeled-form label { color: var(--color-gray); font-size: 0.9rem; - padding-right: 0.25rem; + grid-column: 1; text-align: right; + white-space: nowrap; } -form label.required { +.labeled-form label.required { font-weight: bold; } -form label.error, -form td.error::after { +/* Don't style `label.error + input` if case already covered by input:invalid */ +.labeled-form label.error { color: var(--color-red); } -form td.error { - display: -webkit-box; -} -form td.error::after { - content: attr(data-content); - font-size: 0.9rem; - margin-left: 1rem; - padding: 0.25rem 0; - position: absolute; -} -form em { +.labeled-form em { color: var(--color-text-gray); font-size: 0.75rem; + font-weight: normal; } -form input[type=submit] { +.labeled-form input { + grid-column: 2; +} +.labeled-form input[type=submit] { font-size: 1rem; - margin: 1.5rem auto 0 auto; - padding: 0.75rem; + margin: 1.5em auto 0 auto; + padding: 0.75em; } -/* TODO: remove .items class and make 'form table' work properly */ +/* TODO: remove .items class (?) and make 'form table' work properly */ table.items { border-spacing: 0; border: solid 1px var(--color-border-gray); @@ -588,9 +591,6 @@ form table.items td:first-child { fill: var(--color-border-gray) !important; pointer-events: none; } -.unwrappable { - white-space: nowrap; -} details { diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 8071379..6d6d4e5 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,6 +1,11 @@ class RegistrationsController < Devise::RegistrationsController before_action :authenticate_user!, only: [:edit, :update, :destroy] + def destroy + # TODO: Disallow/disable deletion for last admin account; update :edit view + super + end + protected def update_resource(resource, params) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ebdd181..71d4d4c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,59 +1,84 @@ module ApplicationHelper - # TODO: replace legacy content_tag with tag.tagname - class LabelledFormBuilder < ActionView::Helpers::FormBuilder - (field_helpers - [:label]).each do |selector| + class LabeledFormBuilder < ActionView::Helpers::FormBuilder + (field_helpers - [:label, :hidden_field]).each do |selector| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) - labelled_row_for(method, options) { super } + labeled_field_for(method, options) { super } end RUBY_EVAL end def select(method, choices = nil, options = {}, html_options = {}) - labelled_row_for(method, options) { super } - end - - def submit(value, options = {}) - @template.content_tag :tr do - @template.content_tag :td, super, colspan: 2 - end - end - - def form_for(&block) - @template.content_tag(:table, class: "centered") { yield(self) } + - # Display leftover error messages (there shouldn't be any) - @template.content_tag(:div, @object&.errors.full_messages.join(@template.tag(:br))) + labeled_field_for(method, options) { super } end private - def labelled_row_for(method, options) - @template.content_tag :tr do - @template.content_tag(:td, label_for(method, options), class: "unwrappable") + - @template.content_tag(:td, options.delete(:readonly) ? @object.public_send(method) : yield, - @object&.errors[method].present? ? - {class: "error", data: {content: @object&.errors.delete(method).join(" and ")}} : - {}) - end + def labeled_field_for(method, options) + field = if options.delete(:readonly) then + value = object.public_send(method) + value = @template.l(value) if value.respond_to?(:strftime) + value ||= options[:placeholder] + else + yield + end + label_for(method, options) + field end def label_for(method, options = {}) - return '' if options[:label] == false - - text = options.delete(:label) - text ||= @object.class.human_attribute_name(method).capitalize classes = @template.class_names(required: options[:required], - error: @object&.errors[method].present?) - label = label(method, "#{text}:", class: classes) - hint = options.delete(:hint) + error: object.errors[method].present?) - label + (@template.tag(:br) + @template.content_tag(:em, hint) if hint) + handler = {missing_interpolation_argument_handler: method(:interpolation_missing)} + # Label translation search order: + # controller.action.* => helpers.label.model.* => activerecord.attributes.model.* + # First 2 levels are translated recursively. + label(method, class: classes) do |builder| + translation = I18n.config.with(**handler) { deep_translate(method, **options) } + translation.presence || "#{builder.translation}:" + end + end + + def interpolation_missing(key, values, string) + @template.instance_values[key.to_s] || deep_translate(key, **values) + end + + # Extension to label text translation: + # * recursive translation, + # * interpolation of (_relative_) translation key names and instance variables, + # * pluralization based on any interpolable value + # TODO: add unit tests for the above + def deep_translate(key, **options) + options[:default] = [ + :".#{key}", + :"helpers.label.#{@object_name}.#{key}_html", + :"helpers.label.#{@object_name}.#{key}", + "" + ] + + # At least 1 interpolation key is required for #translate to act on + # missing interpolation arguments (i.e. call custom handler). + # Also setting `key` to nil avoids recurrent translation. + options[key] = nil + + @template.t(".#{key}_html", **options) do |translation, resolved_key| + if translation.is_a?(Array) && (count = translation.to_h[:count]) + @template.t(resolved_key, count: I18n.interpolate(count, **options), **options) + else + translation + end + end end end - def labelled_form_for(record, options = {}, &block) - options = options.deep_merge(builder: LabelledFormBuilder, data: {turbo: false}) - form_for(record, **options) { |f| f.form_for(&block) } + def labeled_form_for(record, options = {}, &block) + extra_options = {builder: LabeledFormBuilder, + data: {turbo: false}, + html: {class: 'labeled-form'}} + options = options.deep_merge(extra_options) do |key, left, right| + key == :class ? class_names(left, right) : right + end + form_for(record, **options, &block) end class TabularFormBuilder < ActionView::Helpers::FormBuilder @@ -111,6 +136,7 @@ module ApplicationHelper # and the first input gets focus. record_object, options = nil, record_object if record_object.is_a?(Hash) options.merge!(builder: TabularFormBuilder, skip_default_ids: true) + # TODO: set error message with setCustomValidity instead of rendering to flash? render_errors(record_object || record_name) fields_for(record_name, record_object, **options, &block) end @@ -122,7 +148,7 @@ module ApplicationHelper end def svg_tag(source, label = nil, options = {}) - svg_tag = content_tag :svg, options do + svg_tag = tag.svg(options) do tag.use(href: "#{image_path(source + ".svg")}#icon") end label.blank? ? svg_tag : svg_tag + tag.span(label) @@ -170,17 +196,23 @@ module ApplicationHelper def image_link_to_unless_current(name, image = nil, options = nil, html_options = {}) name, html_options = link_or_button_options(:link, name, image, html_options) - html_options = html_options.deep_merge DISABLED_ATTRIBUTES if current_page?(options) + # NOTE: Starting from Rails 8.1.0, below condition can be replaced with: + # current_page?(options, method: [:get, :post]) + if request.path == url_for(options) + html_options = html_options.deep_merge DISABLED_ATTRIBUTES + end link_to name, options, html_options end def render_errors(records) - flash[:alert] ||= [] + # Conversion of flash to Array only required because of Devise + flash[:alert] = Array(flash[:alert]) Array(records).each { |record| flash[:alert] += record.errors.full_messages } end def render_flash_messages flash.map do |entry, messages| + # Conversion of flash to Array only required because of Devise Array(messages).map do |message| tag.div class: "flash #{entry}" do tag.div(sanitize(message)) + tag.button(sanitize("×"), tabindex: -1, @@ -203,8 +235,6 @@ module ApplicationHelper private def link_or_button_options(type, name, image = nil, html_options) - name = svg_tag("pictograms/#{image}", name) if image - html_options[:class] = class_names( html_options[:class], 'button', @@ -215,9 +245,10 @@ module ApplicationHelper html_options[:onclick] = "return confirm('\#{html_options[:onclick][:confirm]}');" end - if type == :link && !(html_options[:onclick] || html_options.dig(:data, :turbo_stream)) - name += '...' - end + link_is_local = html_options[:onclick] || html_options.dig(:data, :turbo_stream) + name = name.to_s + name += '...' if type == :link && !link_is_local + name = svg_tag("pictograms/#{image}", name) if image [name, html_options] end diff --git a/app/views/default/units/index.html.erb b/app/views/default/units/index.html.erb index 13c5a3e..f545d3c 100644 --- a/app/views/default/units/index.html.erb +++ b/app/views/default/units/index.html.erb @@ -1,16 +1,17 @@ -
+
<% if current_user.at_least(:active) %> <%# TODO: implement Import all %> <%#= image_button_to t('.import_all'), 'download-multiple-outline', import_all_default_units_path, data: {turbo_stream: true} %> <% end %> - <%= image_link_to t('.back'), 'arrow-left-bold-outline', units_path, class: 'tools' %> + <%= image_link_to t('.back'), 'arrow-left-bold-outline', units_path, + class: 'tools-area' %>
- +
- + <% if current_user.at_least(:active) %> <% end %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7cad348..293a20b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -47,7 +47,7 @@ <%= render_flash_messages %> - <%# Allow overwriting/clearing navigation menu for some views %> + <%# Allows overwriting/clearing navigation menu for some views %> diff --git a/app/views/measurements/_form.html.erb b/app/views/measurements/_form.html.erb index 7976247..8f1470a 100644 --- a/app/views/measurements/_form.html.erb +++ b/app/views/measurements/_form.html.erb @@ -1,5 +1,5 @@ <%= tabular_form_with model: Measurement.new, id: :measurement_form, - class: 'topside vflex', html: {onkeydown: 'formProcessKey(event)'} do |form| %> + class: 'topside-area vflex', html: {onkeydown: 'formProcessKey(event)'} do |form| %>
<%= User.human_attribute_name(:symbol).capitalize %><%= Unit.human_attribute_name(:symbol) %><%= t '.actions' %>
diff --git a/app/views/measurements/index.html.erb b/app/views/measurements/index.html.erb index 4951c46..bac10e1 100644 --- a/app/views/measurements/index.html.erb +++ b/app/views/measurements/index.html.erb @@ -1,5 +1,5 @@ <%# TODO: show hint when no quantities/units defined %> -
+
<% if current_user.at_least(:active) %> <%= image_link_to t('.new_measurement'), 'plus-outline', new_measurement_path, id: :new_measurement_link, onclick: 'this.blur();', @@ -7,7 +7,7 @@ <% end %>
- +
<%= render(@measurements) || render_no_items %> diff --git a/app/views/quantities/index.html.erb b/app/views/quantities/index.html.erb index ea42e34..6d89ed4 100644 --- a/app/views/quantities/index.html.erb +++ b/app/views/quantities/index.html.erb @@ -1,20 +1,20 @@ -
+
<% if current_user.at_least(:active) %> <%= image_link_to t('.new_quantity'), 'plus-outline', new_quantity_path, id: dom_id(Quantity, :new, :link), onclick: 'this.blur();', data: {turbo_stream: true} %> <% end %> <%#= image_link_to t('.import_quantities'), 'download-outline', default_quantities_path, - class: 'tools' %> + class: 'tools-area' %>
-<%= tag.div class: 'main', id: :quantity_form %> +<%= tag.div class: 'main-area', id: :quantity_form %> -
+
- - + + <% if current_user.at_least(:active) %> diff --git a/app/views/units/index.html.erb b/app/views/units/index.html.erb index 8b42c2d..ffc103d 100644 --- a/app/views/units/index.html.erb +++ b/app/views/units/index.html.erb @@ -1,19 +1,20 @@ -
+
<% if current_user.at_least(:active) %> <%= image_link_to t('.new_unit'), 'plus-outline', new_unit_path, id: dom_id(Unit, :new, :link), onclick: 'this.blur();', data: {turbo_stream: true} %> <% end %> - <%= image_link_to t('.import_units'), 'download-outline', default_units_path, class: 'tools' %> + <%= image_link_to t('.import_units'), 'download-outline', default_units_path, + class: 'tools-area' %>
<%= tag.div id: :unit_form %> -
<%= Quantity.human_attribute_name(:name).capitalize %><%= Quantity.human_attribute_name(:description).capitalize %><%= Quantity.human_attribute_name(:name) %><%= Quantity.human_attribute_name(:description) %><%= t :actions %>
+
- - - + + + <% if current_user.at_least(:active) %> diff --git a/app/views/users/confirmations/new.html.erb b/app/views/users/confirmations/new.html.erb index be665d6..03853b5 100644 --- a/app/views/users/confirmations/new.html.erb +++ b/app/views/users/confirmations/new.html.erb @@ -1,8 +1,9 @@ -
- <%= labelled_form_for resource, url: user_confirmation_path do |f| %> - <%= f.email_field :email, required: true, size: 30, autofocus: true, autocomplete: "email", - value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> +<%= labeled_form_for resource, url: user_confirmation_path, + html: {class: 'main-area'} do |f| %> - <%= f.submit t(:resend_confirmation) %> - <% end %> -
+ <%= f.email_field :email, required: true, size: 30, autofocus: true, + autocomplete: 'email', value: + resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email %> + + <%= f.submit t(:resend_confirmation) %> +<% end %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index a635b2a..039abe8 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,12 +1,10 @@ -
<%= User.human_attribute_name(:symbol).capitalize %><%= User.human_attribute_name(:description).capitalize %><%= User.human_attribute_name(:multiplier).capitalize %><%= Unit.human_attribute_name(:symbol) %><%= Unit.human_attribute_name(:description) %><%= Unit.human_attribute_name(:multiplier) %><%= t :actions %>
+
- - - - + + + + @@ -19,18 +17,18 @@ <%= user.status %> <% else %> <%= form_for user do |u| %> - <%= u.select :status, User.statuses.keys, {}, autocomplete: "off", - onchange: "this.form.requestSubmit();" %> + <%= u.select :status, User.statuses.keys, {}, autocomplete: 'off', + onchange: 'this.form.requestSubmit();' %> <% end %> <% end %> - + diff --git a/app/views/users/mailer/confirmation_instructions.html.erb b/app/views/users/mailer/confirmation_instructions.html.erb index dc55f64..79b4382 100644 --- a/app/views/users/mailer/confirmation_instructions.html.erb +++ b/app/views/users/mailer/confirmation_instructions.html.erb @@ -1,5 +1,5 @@

Welcome <%= @email %>!

You can confirm your account email through the link below:

- +

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

diff --git a/app/views/users/passwords/edit.html.erb b/app/views/users/passwords/edit.html.erb index 8853076..d79707d 100644 --- a/app/views/users/passwords/edit.html.erb +++ b/app/views/users/passwords/edit.html.erb @@ -1,13 +1,12 @@ -
- <%= labelled_form_for resource, url: user_password_path, html: {method: :put} do |f| %> - <%= f.hidden_field :reset_password_token, label: false %> +<%= labeled_form_for resource, url: user_password_path, + html: {method: :put, class: 'main-area'} do |f| %> - <%= f.password_field :password, label: t(".new_password"), required: true, size: 30, - minlength: @minimum_password_length, autofocus: true, autocomplete: "new-password", - hint: t("users.minimum_password_length", count: @minimum_password_length) %> - <%= f.password_field :password_confirmation, label: t(".password_confirmation"), - required: true, size: 30, minlength: @minimum_password_length, autocomplete: "off" %> + <%= f.hidden_field :reset_password_token %> - <%= f.submit t(".update_password") %> - <% end %> -
+ <%= f.password_field :password, required: true, size: 30, autofocus: true, + minlength: @minimum_password_length, autocomplete: 'new-password' %> + <%= f.password_field :password_confirmation, required: true, size: 30, + minlength: @minimum_password_length, autocomplete: 'off' %> + + <%= f.submit t('.update_password') %> +<% end %> diff --git a/app/views/users/passwords/new.html.erb b/app/views/users/passwords/new.html.erb index 62dd179..b1a1a28 100644 --- a/app/views/users/passwords/new.html.erb +++ b/app/views/users/passwords/new.html.erb @@ -1,7 +1,8 @@ -
- <%= labelled_form_for resource, url: user_password_path do |f| %> - <%= f.email_field :email, required: true, size: 30, autofocus: true, autocomplete: "email" %> +<%= labeled_form_for resource, url: user_password_path, + html: {class: 'main-area'} do |f| %> - <%= f.submit t(:recover_password) %> - <% end %> -
+ <%= f.email_field :email, required: true, size: 30, autofocus: true, + autocomplete: 'email' %> + + <%= f.submit t(:recover_password) %> +<% end %> diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb index dd79762..9e1907a 100644 --- a/app/views/users/registrations/edit.html.erb +++ b/app/views/users/registrations/edit.html.erb @@ -1,31 +1,30 @@ <% content_for :navigation, flush: true do %> - <%= link_to svg_tag("pictograms/arrow-left-bold-outline", t(:back)), + <%= link_to svg_tag('pictograms/arrow-left-bold-outline', t(:back)), request.referer.present? ? :back : root_path, class: 'tab' %> <% end %> -
- <%= image_button_to t(".delete"), "account-remove-outline", user_registration_path, - form_class: 'tools', method: :delete, data: {turbo: false}, - onclick: {confirm: t(".confirm_delete")} %> +
+ <%#= TODO: Disallow/disable deletion for last admin account, image_button_to_if %> + <%= image_button_to t('.delete'), 'account-remove-outline', user_registration_path, + form_class: 'tools-area', method: :delete, data: {turbo: false}, + onclick: {confirm: t('.confirm_delete')} %>
-<%= labelled_form_for resource, url: registration_path(resource), - html: {method: :patch, class: 'main'} do |f| %> - <%= f.email_field :email, size: 30, autofocus: true, autocomplete: "off" %> - <% if f.object.pending_reconfirmation? %> - <%= f.text_field :unconfirmed_email, readonly: true, tabindex: -1, - hint: t(".unconfirmed_email_hint", - timestamp: f.object.confirmation_sent_at.to_fs(:db_without_sec)) %> +<%= labeled_form_for resource, url: registration_path(resource), + html: {method: :patch, class: 'main-area'} do |f| %> + + <%= f.email_field :email, size: 30, autofocus: true, autocomplete: 'off' %> + <% if resource.pending_reconfirmation? %> + <%= f.text_field :unconfirmed_email, readonly: true, + confirmation_sent_at: l(resource.confirmation_sent_at) %> <% end %> <%= f.select :status, User.statuses, readonly: true %> - <%= f.password_field :password, label: t(".new_password"), size: 30, - minlength: @minimum_password_length, autocomplete: "new-password", - hint: t(".blank_password_hint_html", - subhint: t("users.minimum_password_length", count: @minimum_password_length)) %> - <%= f.password_field :password_confirmation, label: t(".password_confirmation"), - size: 30, minlength: @minimum_password_length, autocomplete: "off" %> + <%= f.password_field :password, size: 30, autocomplete: 'new-password', + minlength: @minimum_password_length %> + <%= f.password_field :password_confirmation, size: 30, autocomplete: 'off', + minlength: @minimum_password_length %> - <%= f.submit t(".update") %> + <%= f.submit %> <% end %> diff --git a/app/views/users/registrations/new.html.erb b/app/views/users/registrations/new.html.erb index 1832859..7379e18 100644 --- a/app/views/users/registrations/new.html.erb +++ b/app/views/users/registrations/new.html.erb @@ -1,16 +1,16 @@ -
- <%= labelled_form_for resource, url: user_registration_path do |f| %> - <%= f.email_field :email, required: true, size: 30, autofocus: true, autocomplete: "email" %> +
+ <%= labeled_form_for resource, url: user_registration_path do |f| %> + <%= f.email_field :email, required: true, size: 30, autofocus: true, + autocomplete: 'email' %> <%= f.password_field :password, required: true, size: 30, - minlength: @minimum_password_length, autocomplete: "new-password", - hint: t("users.minimum_password_length", count: @minimum_password_length) %> - <%= f.password_field :password_confirmation, label: t(".password_confirmation"), - required: true, size: 30, minlength: @minimum_password_length, autocomplete: "off" %> + minlength: @minimum_password_length, autocomplete: 'new-password' %> + <%= f.password_field :password_confirmation, required: true, size: 30, + minlength: @minimum_password_length, autocomplete: 'off' %> <%= f.submit t(:register) %> <% end %> - <%= content_tag :p, t(:or), style: "text-align: center;" %> - <%= image_link_to t(:resend_confirmation), "email-sync-outline", new_user_confirmation_path, - class: "centered" %> + <%= content_tag :p, t(:or), style: 'text-align: center;' %> + <%= image_link_to t(:resend_confirmation), 'email-sync-outline', + new_user_confirmation_path, class: 'centered' %>
diff --git a/app/views/users/sessions/new.html.erb b/app/views/users/sessions/new.html.erb index c8b9e78..3af3232 100644 --- a/app/views/users/sessions/new.html.erb +++ b/app/views/users/sessions/new.html.erb @@ -1,17 +1,18 @@ -
- <%= labelled_form_for resource, url: user_session_path do |f| %> - <%= f.email_field :email, required: true, size: 30, autofocus: true, autocomplete: "email" %> - <%= f.password_field :password, required: true, size: 30, minlength: @minimum_password_length, - autocomplete: "current-password" %> +
+ <%= labeled_form_for resource, url: user_session_path do |f| %> + <%= f.email_field :email, required: true, size: 30, autofocus: true, + autocomplete: 'email' %> + <%= f.password_field :password, required: true, size: 30, + minlength: @minimum_password_length, autocomplete: 'current-password' %> <% if devise_mapping.rememberable? %> - <%= f.check_box :remember_me, label: t(".remember_me") %> + <%= f.check_box :remember_me %> <% end %> <%= f.submit t(:sign_in) %> <% end %> - <%= content_tag :p, t(:or), style: "text-align: center;" %> + <%= content_tag :p, t(:or), style: 'text-align: center;' %> <%= image_link_to t(:recover_password), 'lock-reset', new_user_password_path, class: 'centered' %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index d06452c..320834e 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -1,19 +1,18 @@ <% content_for :navigation, flush: true do %> -
- <%= image_link_to t(:back), "arrow-left-bold-outline", users_path %> -
+ <%= link_to svg_tag('pictograms/arrow-left-bold-outline', t(:back)), users_path, + class: 'tab' %> <% end %> -<%= labelled_form_for @user do |f| %> +<%= labeled_form_for @user, html: {class: 'main-area'} do |f| %> <%= f.email_field :email, readonly: true %> - <% if f.object.pending_reconfirmation? %> + <% if @user.pending_reconfirmation? %> <%= f.email_field :unconfirmed_email, readonly: true, - hint: t("users.registrations.edit.unconfirmed_email_hint", - timestamp: f.object.confirmation_sent_at.to_fs(:db_without_sec)) %> + confirmation_sent_at: l(@user.confirmation_sent_at) %> <% end %> + <%# TODO: allow status change here? %> <%= f.select :status, User.statuses, readonly: true %> <%= f.text_field :created_at, readonly: true %> - <%= f.text_field :confirmed_at, readonly: true %> + <%= f.text_field :confirmed_at, readonly: true, placeholder: t(:no) %> <% end %> diff --git a/config/initializers/time_formats.rb b/config/initializers/time_formats.rb deleted file mode 100644 index 342fb9d..0000000 --- a/config/initializers/time_formats.rb +++ /dev/null @@ -1,3 +0,0 @@ -# Format contains non-breaking space: -# 160.chr(Encoding::UTF_8) -Time::DATE_FORMATS[:db_without_sec] = "%Y-%m-%d %H:%M" diff --git a/config/locales/en.yml b/config/locales/en.yml index 2d4286a..6d09873 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,22 +1,28 @@ en: + time: + formats: + # Format contains non-breaking space: 160.chr(Encoding::UTF_8) + default: "%Y-%m-%d %H:%M %Z" + without_tz: "%Y-%m-%d %H:%M" errors: messages: precision_exceeded: must not exceed %{value} significant digits scale_exceeded: must not exceed %{value} decimal digits activerecord: attributes: - unit: - symbol: Symbol + quantity: + description: Description name: Name - multiplier: Multiplier + unit: base: Base unit + description: Description + multiplier: Multiplier + symbol: Symbol user: - email: e-mail - status: status - password: password - created_at: registered - confirmed_at: confirmed - unconfirmed_email: Awaiting confirmation for + confirmed_at: Confirmed + created_at: Registered + email: E-mail + status: Status errors: models: unit: @@ -53,9 +59,22 @@ en: The request is semantically incorrect and was rejected (422 Unprocessable Entity). This should not happen, please notify site administrator. helpers: + label: + user: + password_confirmation: 'Retype new password:' + password_length_hint_html: + count: '%{minimum_password_length}' + zero: + one:
(%{count} character minimum) + other:
(%{count} characters minimum) + remember_me: 'Remember me:' + unconfirmed_email_html: > + Awaiting confirmation for:
(since %{confirmation_sent_at}) submit: create: Create update: Update + user: + update: Update profile layouts: application: issue_tracker: Report issue @@ -129,34 +148,27 @@ en: disguise: View as passwords: edit: - new_password: New password - password_confirmation: Retype new password + password_html: 'New password:%{password_length_hint_html}' update_password: Update password registrations: new: - password_confirmation: Retype password + password_html: 'Password:%{password_length_hint_html}' + password_confirmation: 'Retype password:' edit: confirm_delete: Are you sure you want to delete profile? All data will be irretrievably lost. delete: Delete profile - unconfirmed_email_hint: (since %{timestamp}) - new_password: New password - password_confirmation: Retype new password - blank_password_hint_html: leave blank to keep unchanged
%{subhint} - update: Update profile - sessions: - new: - remember_me: Remember me - minimum_password_length: - zero: - one: (%{count} character minimum) - other: (%{count} characters minimum) + password_html: > + New password: +
leave blank to keep unchanged + %{password_length_hint_html} actions: Actions add: Add apply: Apply back: Back cancel: Cancel delete: Delete + :no: 'no' or: or register: Register sign_in: Sign in
<%= User.human_attribute_name(:email).capitalize %><%= User.human_attribute_name(:status).capitalize %><%= User.human_attribute_name(:confirmed_at).capitalize %> - <%= User.human_attribute_name(:created_at).capitalize %> UTC - <%= User.human_attribute_name(:email) %><%= User.human_attribute_name(:status) %><%= User.human_attribute_name(:confirmed_at) %><%= User.human_attribute_name(:created_at) %> (UTC) <%= t :actions %>
- <%= svg_tag "pictograms/checkbox-marked-outline" if user.confirmed_at.present? %> + <%= svg_tag 'pictograms/checkbox-marked-outline' if user.confirmed_at.present? %> <%= user.created_at.to_fs(:db_without_sec) %><%= l user.created_at, format: :without_tz %> <% if allow_disguise?(user) %> - <%= image_link_to t(".disguise"), "incognito", disguise_user_path(user) %> + <%= image_link_to t('.disguise'), 'incognito', disguise_user_path(user) %> <% end %>