forked from fixin.me/fixin.me
Simplify and improve labeled form
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 ")}} :
|
||||
{})
|
||||
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 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 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 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
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<div class="rightside buttongrid">
|
||||
<div class="rightside-area buttongrid">
|
||||
<% 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' %>
|
||||
</div>
|
||||
|
||||
<table class="main items">
|
||||
<table class="main-area items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= User.human_attribute_name(:symbol).capitalize %></th>
|
||||
<th><%= Unit.human_attribute_name(:symbol) %></th>
|
||||
<% if current_user.at_least(:active) %>
|
||||
<th><%= t '.actions' %></th>
|
||||
<% end %>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
<%= render_flash_messages %>
|
||||
</div>
|
||||
|
||||
<%# Allow overwriting/clearing navigation menu for some views %>
|
||||
<%# Allows overwriting/clearing navigation menu for some views %>
|
||||
<nav class="navigation">
|
||||
<%= content_for(:navigation) || (navigation_menu if user_signed_in?) %>
|
||||
</nav>
|
||||
|
||||
@@ -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| %>
|
||||
<table class="items centered">
|
||||
<tbody id="readouts"></tbody>
|
||||
</table>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<%# TODO: show hint when no quantities/units defined %>
|
||||
<div class="rightside buttongrid">
|
||||
<div class="rightside-area buttongrid">
|
||||
<% 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 %>
|
||||
</div>
|
||||
|
||||
<table class="main">
|
||||
<table class="main-area">
|
||||
<tbody id="measurements">
|
||||
<%= render(@measurements) || render_no_items %>
|
||||
</tbody>
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
<div class="rightside buttongrid">
|
||||
<div class="rightside-area buttongrid">
|
||||
<% 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' %>
|
||||
</div>
|
||||
|
||||
<%= tag.div class: 'main', id: :quantity_form %>
|
||||
<%= tag.div class: 'main-area', id: :quantity_form %>
|
||||
|
||||
<table class="main items">
|
||||
<table class="main-area items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= Quantity.human_attribute_name(:name).capitalize %></th>
|
||||
<th><%= Quantity.human_attribute_name(:description).capitalize %></th>
|
||||
<th><%= Quantity.human_attribute_name(:name) %></th>
|
||||
<th><%= Quantity.human_attribute_name(:description) %></th>
|
||||
<% if current_user.at_least(:active) %>
|
||||
<th><%= t :actions %></th>
|
||||
<th></th>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<div class="rightside buttongrid">
|
||||
<div class="rightside-area buttongrid">
|
||||
<% 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' %>
|
||||
</div>
|
||||
|
||||
<%= tag.div id: :unit_form %>
|
||||
|
||||
<table class="main items">
|
||||
<table class="main-area items">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= User.human_attribute_name(:symbol).capitalize %></th>
|
||||
<th><%= User.human_attribute_name(:description).capitalize %></th>
|
||||
<th><%= User.human_attribute_name(:multiplier).capitalize %></th>
|
||||
<th><%= Unit.human_attribute_name(:symbol) %></th>
|
||||
<th><%= Unit.human_attribute_name(:description) %></th>
|
||||
<th><%= Unit.human_attribute_name(:multiplier) %></th>
|
||||
<% if current_user.at_least(:active) %>
|
||||
<th><%= t :actions %></th>
|
||||
<th></th>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<div class="main">
|
||||
<%= 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.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 %>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
<table class="main items" id="users">
|
||||
<table class="main-area items" id="users">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%= User.human_attribute_name(:email).capitalize %></th>
|
||||
<th><%= User.human_attribute_name(:status).capitalize %></th>
|
||||
<th><%= User.human_attribute_name(:confirmed_at).capitalize %></th>
|
||||
<th>
|
||||
<%= User.human_attribute_name(:created_at).capitalize %> <sup>UTC</sup>
|
||||
</th>
|
||||
<th><%= User.human_attribute_name(:email) %></th>
|
||||
<th><%= User.human_attribute_name(:status) %></th>
|
||||
<th><%= User.human_attribute_name(:confirmed_at) %></th>
|
||||
<th><%= User.human_attribute_name(:created_at) %> <sup>(UTC)</sup></th>
|
||||
<th><%= t :actions %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -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 %>
|
||||
</td>
|
||||
<td class="svg">
|
||||
<%= svg_tag "pictograms/checkbox-marked-outline" if user.confirmed_at.present? %>
|
||||
<%= svg_tag 'pictograms/checkbox-marked-outline' if user.confirmed_at.present? %>
|
||||
</td>
|
||||
<td><%= user.created_at.to_fs(:db_without_sec) %></td>
|
||||
<td><%= l user.created_at, format: :without_tz %></td>
|
||||
<td class="actions">
|
||||
<% 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 %>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<p>Welcome <%= @email %>!</p>
|
||||
|
||||
<p>You can confirm your account email through the link below:</p>
|
||||
|
||||
<!-- FIXME: is confirmation_url valid route prefix? -->
|
||||
<p><%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
<div class="main">
|
||||
<%= 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") %>
|
||||
<%= 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 %>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<div class="main">
|
||||
<%= 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.email_field :email, required: true, size: 30, autofocus: true,
|
||||
autocomplete: 'email' %>
|
||||
|
||||
<%= f.submit t(:recover_password) %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -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 %>
|
||||
|
||||
<div class="rightside buttongrid">
|
||||
<%= image_button_to t(".delete"), "account-remove-outline", user_registration_path,
|
||||
form_class: 'tools', method: :delete, data: {turbo: false},
|
||||
onclick: {confirm: t(".confirm_delete")} %>
|
||||
<div class="rightside-area buttongrid">
|
||||
<%#= 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')} %>
|
||||
</div>
|
||||
|
||||
<%= 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 %>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<div class="main">
|
||||
<%= labelled_form_for resource, url: user_registration_path do |f| %>
|
||||
<%= f.email_field :email, required: true, size: 30, autofocus: true, autocomplete: "email" %>
|
||||
<div class="main-area">
|
||||
<%= 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' %>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<div class="main">
|
||||
<%= 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" %>
|
||||
<div class="main-area">
|
||||
<%= 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' %>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
<% content_for :navigation, flush: true do %>
|
||||
<div class="left">
|
||||
<%= image_link_to t(:back), "arrow-left-bold-outline", users_path %>
|
||||
</div>
|
||||
<%= 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 %>
|
||||
|
||||
@@ -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"
|
||||
@@ -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: <br><em>(%{count} character minimum)</em>
|
||||
other: <br><em>(%{count} characters minimum)</em>
|
||||
remember_me: 'Remember me:'
|
||||
unconfirmed_email_html: >
|
||||
Awaiting confirmation for:<br><em>(since %{confirmation_sent_at})</em>
|
||||
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<br>%{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:
|
||||
<br><em>leave blank to keep unchanged</em>
|
||||
%{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
|
||||
|
||||
Reference in New Issue
Block a user