diff --git a/.rubocop.yml b/.rubocop.yml index e3db44c2e..8d8e8a64b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,5 @@ +inherit_from: .rubocop_todo.yml + AllCops: Exclude: - 'bin/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 000000000..30efa87e8 --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,43 @@ +# This configuration was generated by +# `rubocop --auto-gen-config` +# on 2025-11-04 02:06:37 UTC using RuboCop version 1.81.6. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 52 +# This cop supports safe autocorrection (--autocorrect). +Lint/RedundantCopDisableDirective: + Enabled: false + +# Offense count: 2 +# Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns. +Metrics/MethodLength: + Max: 14 + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. +# SupportedStyles: assign_to_condition, assign_inside_condition +Style/ConditionalAssignment: + Exclude: + - 'app/controllers/better_together/joatu/hub_controller.rb' + +# Offense count: 29 +# This cop supports safe autocorrection (--autocorrect). +Style/IfUnlessModifier: + Enabled: false + +# Offense count: 1 +# Configuration parameters: Max. +Style/SafeNavigationChainLength: + Exclude: + - 'spec/features/devise/registration_spec.rb' + +# Offense count: 1 +# This cop supports safe autocorrection (--autocorrect). +# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. +# URISchemes: http, https +Layout/LineLength: + Max: 145 diff --git a/Gemfile.lock b/Gemfile.lock index 195b61b11..59849fe48 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,6 +29,7 @@ PATH devise devise-i18n devise-jwt + devise_zxcvbn elasticsearch-model (~> 7) elasticsearch-rails (~> 7) font-awesome-sass (~> 6.5) @@ -266,6 +267,9 @@ GEM devise-jwt (0.12.1) devise (~> 4.0) warden-jwt_auth (~> 0.10) + devise_zxcvbn (6.0.0) + devise + zxcvbn (~> 0.1.7) diff-lcs (1.6.2) disposable (0.6.3) declarative (>= 0.0.9, < 1.0.0) @@ -832,6 +836,7 @@ GEM xpath (3.2.0) nokogiri (~> 1.8) zeitwerk (2.7.3) + zxcvbn (0.1.13) PLATFORMS aarch64-linux diff --git a/app/controllers/better_together/users/registrations_controller.rb b/app/controllers/better_together/users/registrations_controller.rb index 256c727b7..faf140b26 100644 --- a/app/controllers/better_together/users/registrations_controller.rb +++ b/app/controllers/better_together/users/registrations_controller.rb @@ -7,6 +7,7 @@ class RegistrationsController < ::Devise::RegistrationsController # rubocop:todo include DeviseLocales skip_before_action :check_platform_privacy + before_action :configure_permitted_parameters before_action :set_required_agreements, only: %i[new create] before_action :set_event_invitation_from_session, only: %i[new create] before_action :configure_account_update_params, only: [:update] @@ -63,18 +64,12 @@ def update # rubocop:todo Metrics/AbcSize, Metrics/MethodLength def new super do |user| - # Pre-fill email from platform invitation - user.email = @platform_invitation.invitee_email if @platform_invitation && user.email.empty? - - if @event_invitation - # Pre-fill email from event invitation - user.email = @event_invitation.invitee_email if @event_invitation && user.email.empty? - user.person = @event_invitation.invitee if @event_invitation.invitee.present? - end + setup_user_from_invitations(user) + user.build_person unless user.person end end - def create # rubocop:todo Metrics/MethodLength + def create # rubocop:todo Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity unless agreements_accepted? handle_agreements_not_accepted return @@ -87,11 +82,25 @@ def create # rubocop:todo Metrics/MethodLength return end + # Use transaction for all user creation and associated records ActiveRecord::Base.transaction do - super do |user| - handle_user_creation(user) if user.persisted? + # Call Devise's default create behavior + super + + # Handle post-registration setup if user was created successfully + if resource.persisted? && resource.errors.empty? + handle_user_creation(resource) + elsif resource.persisted? + # User was created but has errors - rollback to maintain consistency + raise ActiveRecord::Rollback end end + rescue ActiveRecord::RecordInvalid, ActiveRecord::InvalidForeignKey => e + # Clean up and show user-friendly error + Rails.logger.error "Registration failed: #{e.message}" + build_resource(sign_up_params) if resource.nil? + resource&.errors&.add(:base, 'Registration could not be completed. Please try again.') + respond_with resource end protected @@ -105,6 +114,10 @@ def configure_account_update_params keys: %i[email password password_confirmation current_password]) end + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: [person_attributes: %i[identifier name description]]) + end + def set_required_agreements @privacy_policy_agreement = BetterTogether::Agreement.find_by(identifier: 'privacy_policy') @terms_of_service_agreement = BetterTogether::Agreement.find_by(identifier: 'terms_of_service') @@ -170,32 +183,116 @@ def handle_agreements_not_accepted respond_with resource end + def setup_user_from_invitations(user) + # Pre-fill email from platform invitation + user.email = @platform_invitation.invitee_email if @platform_invitation && user.email.empty? + + return unless @event_invitation + + # Pre-fill email from event invitation + user.email = @event_invitation.invitee_email if @event_invitation && user.email.empty? + user.person = @event_invitation.invitee if @event_invitation.invitee.present? + end + def handle_user_creation(user) - setup_person_for_user(user) - return unless user.save! + return unless event_invitation_person_updated?(user) + # Reload user to ensure all nested attributes and associations are properly loaded user.reload - setup_community_membership(user) + person = user.person + + return unless person_persisted?(user, person) + + setup_community_membership(user, person) handle_platform_invitation(user) handle_event_invitation(user) - create_agreement_participants(user.person) + create_agreement_participants(person) + end + + def event_invitation_person_updated?(user) + return true unless @event_invitation&.invitee.present? + + return true if user.person.update(person_params) + + Rails.logger.error "Failed to update person for event invitation: #{user.person.errors.full_messages}" + false + end + + def person_persisted?(user, person) + return true if person&.persisted? + + Rails.logger.error "Person not found or not persisted for user #{user.id}" + false end def setup_person_for_user(user) - if @event_invitation && @event_invitation.invitee.present? - user.person = @event_invitation.invitee - user.person.update(person_params) - else - user.build_person(person_params) - end + return update_existing_person_for_event(user) if @event_invitation&.invitee.present? + + create_new_person_for_user(user) + end + + def update_existing_person_for_event(user) + user.person = @event_invitation.invitee + return if user.person.update(person_params) + + Rails.logger.error "Failed to update person for event invitation: #{user.person.errors.full_messages}" + user.errors.add(:person, 'Could not update person information') end - def setup_community_membership(user) + def create_new_person_for_user(user) + return handle_empty_person_params(user) if person_params.empty? + + user.build_person(person_params) + return unless person_validated_and_saved?(user) + + save_person_identification(user) + end + + def handle_empty_person_params(user) + Rails.logger.error 'Person params are empty, cannot build person' + user.errors.add(:person, 'Person information is required') + end + + def person_validated_and_saved?(user) + return save_person?(user) if user.person.valid? + + Rails.logger.error "Person validation failed: #{user.person.errors.full_messages}" + user.errors.add(:person, 'Person information is invalid') + false + end + + def save_person?(user) + return true if user.person.save + + Rails.logger.error "Failed to save person: #{user.person.errors.full_messages}" + user.errors.add(:person, 'Could not save person information') + false + end + + def save_person_identification(user) + person_identification = user.person_identification + return if person_identification&.save + + Rails.logger.error "Failed to save person identification: #{person_identification&.errors&.full_messages}" + user.errors.add(:person, 'Could not link person to user') + end + + def setup_community_membership(user, person_param = nil) + person = person_param || user.person community_role = determine_community_role - helpers.host_community.person_community_memberships.find_or_create_by!( - member: user.person, - role: community_role - ) + + begin + helpers.host_community.person_community_memberships.find_or_create_by!( + member: person, + role: community_role + ) + rescue ActiveRecord::InvalidForeignKey => e + Rails.logger.error "Foreign key violation creating community membership: #{e.message}" + raise e + rescue StandardError => e + Rails.logger.error "Unexpected error creating community membership: #{e.message}" + raise e + end end def handle_platform_invitation(user) @@ -235,10 +332,18 @@ def after_update_path_for(_resource) end def person_params + return {} unless params[:user] && params[:user][:person_attributes] + params.require(:user).require(:person_attributes).permit(%i[identifier name description]) + rescue ActionController::ParameterMissing => e + Rails.logger.error "Missing person parameters: #{e.message}" + {} end def agreements_accepted? + # Ensure required agreements are set + set_required_agreements if @privacy_policy_agreement.nil? + required = [params[:privacy_policy_agreement], params[:terms_of_service_agreement]] # If a code of conduct agreement exists, require it as well required << params[:code_of_conduct_agreement] if @code_of_conduct_agreement.present? @@ -247,11 +352,21 @@ def agreements_accepted? end def create_agreement_participants(person) + unless person&.persisted? + Rails.logger.error 'Cannot create agreement participants - person not persisted' + return + end + identifiers = %w[privacy_policy terms_of_service] identifiers << 'code_of_conduct' if BetterTogether::Agreement.exists?(identifier: 'code_of_conduct') agreements = BetterTogether::Agreement.where(identifier: identifiers) + agreements.find_each do |agreement| - BetterTogether::AgreementParticipant.create!(agreement: agreement, person: person, accepted_at: Time.current) + BetterTogether::AgreementParticipant.create!( + agreement: agreement, + person: person, + accepted_at: Time.current + ) end end end diff --git a/app/models/better_together/identification.rb b/app/models/better_together/identification.rb index 2280e7241..2c5ce138c 100644 --- a/app/models/better_together/identification.rb +++ b/app/models/better_together/identification.rb @@ -10,6 +10,8 @@ class Identification < ApplicationRecord polymorphic: true, autosave: true + accepts_nested_attributes_for :identity + validates :identity, presence: true validates :agent, diff --git a/app/models/better_together/user.rb b/app/models/better_together/user.rb index ba815960c..8f89f3562 100644 --- a/app/models/better_together/user.rb +++ b/app/models/better_together/user.rb @@ -8,6 +8,7 @@ class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, + :zxcvbnable, :recoverable, :rememberable, :validatable, :confirmable, :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist @@ -32,31 +33,40 @@ class User < ApplicationRecord delegate :permitted_to?, to: :person, allow_nil: true def build_person(attributes = {}) - build_person_identification( + identification = build_person_identification( agent: self, - identity: ::BetterTogether::Person.new(attributes) - )&.identity + identity_type: 'BetterTogether::Person', + active: true + ) + person = ::BetterTogether::Person.new(attributes) + identification.identity = person + person end - # Define person_attributes method to get attributes of associated Person + # Override person method to ensure it builds if needed def person - # Check if a Person object exists and return its attributes - super.present? ? super : build_person + person_identification&.identity end - # def person= arg - # build_person_identification( - # agent: self, - # identity: arg - # )&.identity - # end + # Custom person= method + def person=(person_obj) + if person_identification + person_identification.identity = person_obj + else + build_person_identification( + agent: self, + identity: person_obj, + identity_type: 'BetterTogether::Person', + active: true + ) + end + end - # Define person_attributes= method + # Define person_attributes= method for nested attributes def person_attributes=(attributes) - # Check if a Person object already exists - if person.present? + if person_identification&.identity # Update existing Person object - person.update(attributes) + person_identification.identity.assign_attributes(attributes) else # Build new Person object if it doesn't exist build_person(attributes) @@ -66,5 +76,12 @@ def person_attributes=(attributes) def to_s email end + + # TODO: accessing person here was causing save issues in the registration process due the the autobuild + # def weak_words + # return [] unless person + + # [person.name, person.slug, person.identifier] + # end end end diff --git a/app/views/better_together/joatu/offers/_offer_list_item.html.erb b/app/views/better_together/joatu/offers/_offer_list_item.html.erb index b1b5e3603..c96c5f70b 100644 --- a/app/views/better_together/joatu/offers/_offer_list_item.html.erb +++ b/app/views/better_together/joatu/offers/_offer_list_item.html.erb @@ -14,7 +14,7 @@ <% response_link = joatu_offer.response_links_as_response.find { |rl| rl.source&.creator == current_person } rescue nil %> <% if response_link.present? %> <% source_type_key = response_link.source_type.demodulize.underscore rescue 'item' %> - <%= t('better_together.joatu.response_to_your', type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize), default: "Response to your %{type}", type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize)) %> + <%= t('better_together.joatu.response_to_your', type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize)) %> <% end %> <% end %> diff --git a/app/views/better_together/joatu/requests/_request_list_item.html.erb b/app/views/better_together/joatu/requests/_request_list_item.html.erb index 661e1f470..e12f482e8 100644 --- a/app/views/better_together/joatu/requests/_request_list_item.html.erb +++ b/app/views/better_together/joatu/requests/_request_list_item.html.erb @@ -14,7 +14,7 @@ <% response_link = request_model.response_links_as_response.find { |rl| rl.source&.creator == current_person } rescue nil %> <% if response_link.present? %> <% source_type_key = response_link.source_type.demodulize.underscore rescue 'item' %> - <%= t('better_together.joatu.response_to_your', type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize), default: "Response to your %{type}", type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize)) %> + <%= t('better_together.joatu.response_to_your', type: t("better_together.joatu.type.#{source_type_key}", default: response_link.source_type.demodulize)) %> <% end %> <% end %> diff --git a/better_together.gemspec b/better_together.gemspec index f7b86ab58..181229f90 100644 --- a/better_together.gemspec +++ b/better_together.gemspec @@ -37,6 +37,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'devise' spec.add_dependency 'devise-i18n' spec.add_dependency 'devise-jwt' + spec.add_dependency 'devise_zxcvbn' spec.add_dependency 'elasticsearch-model', '~> 7' spec.add_dependency 'elasticsearch-rails', '~> 7' spec.add_dependency 'font-awesome-sass', '~> 6.5' diff --git a/config/locales/en.yml b/config/locales/en.yml index 6a49a1955..a8580eed1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -409,6 +409,7 @@ en: last_sign_in_ip: Last sign in IP lock_version: Lock version locked_at: Locked at + password: Password person: :activerecord.models.person person_identification: :activerecord.models.person_identification remember_created_at: Remember created at @@ -1847,6 +1848,9 @@ en: too_short: one: is too short (minimum is %{count} character) other: is too short (minimum is %{count} characters) + weak_password: not strong enough. Consider adding a number, symbols or more + letters to make it stronger. Avoid using the word 'password', your birthday, + or other common easily-guessable passwords. wrong_length: one: is the wrong length (should be %{count} character) other: is the wrong length (should be %{count} characters) @@ -2041,6 +2045,7 @@ en: en: English es: Español fr: Français + uk: Українська meta: default_description: Welcome to %{platform_name} page: diff --git a/config/locales/es.yml b/config/locales/es.yml index 31edccb05..8d35eafcf 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -409,6 +409,7 @@ es: last_sign_in_ip: IP del último inicio lock_version: Versión de bloqueo locked_at: Fecha de bloqueo + password: Contraseña person: :activerecord.models.person person_identification: :activerecord.models.person_identification remember_created_at: Fecha de 'Recordarme' @@ -1869,6 +1870,9 @@ es: too_short: one: es demasiado corto (%{count} carácter mínimo) other: es demasiado corto (%{count} caracteres mínimo) + weak_password: no es lo suficientemente fuerte. Considera agregar un número, + símbolos o más letras para hacerla más fuerte. Evita usar la palabra 'contraseña', + tu fecha de nacimiento o otras contraseñas comunes fáciles de adivinar. wrong_length: one: no tiene la longitud correcta (%{count} carácter exactos) other: no tiene la longitud correcta (%{count} caracteres exactos) @@ -2067,6 +2071,7 @@ es: en: English es: Español fr: Français + uk: Українська meta: default_description: Bienvenido a %{platform_name} page: diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 60a48b25e..b154fe803 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -409,6 +409,7 @@ fr: last_sign_in_ip: IP de dernière connexion lock_version: Version de verrouillage locked_at: Verrouillé à + password: Mot de passe person: :activerecord.models.person person_identification: :activerecord.models.person_identification remember_created_at: Souvenir créé à @@ -1877,6 +1878,9 @@ fr: too_short: one: est trop court (au moins %{count} caractère) other: est trop court (au moins %{count} caractères) + weak_password: n'est pas assez fort. Considérez ajouter un nombre, des symboles + ou plus de lettres pour le renforcer. Évitez d'utiliser le mot 'mot de passe', + votre date de naissance ou d'autres mots de passe communs faciles à deviner. wrong_length: one: ne fait pas la bonne longueur (doit comporter %{count} caractère) other: ne fait pas la bonne longueur (doit comporter %{count} caractères) @@ -2072,8 +2076,9 @@ fr: calculating: Calcul en cours locales: en: English - es: Espagnol + es: Español fr: Français + uk: Українська meta: default_description: Bienvenue sur %{platform_name} page: diff --git a/config/locales/uk.yml b/config/locales/uk.yml new file mode 100644 index 000000000..44f3b2d3c --- /dev/null +++ b/config/locales/uk.yml @@ -0,0 +1,2326 @@ +--- +uk: + accepted_at: Прийнято о + accessibility_attributes: Атрибути доступності + activerecord: + attributes: + action_text/encrypted_rich_text: + body: Тіло + embeds_attachments: Вбудовані вкладення + embeds_blobs: Вбудовані блоби + locale: Локаль + name: Ім'я + record: :activerecord.models.record + record_type: Тип запису + action_text/rich_text: + body: Тіло + embeds_attachments: Вбудовані вкладення + embeds_blobs: Вбудовані блоби + locale: Локаль + name: Ім'я + record: :activerecord.models.record + record_type: Тип запису + active_storage/attachment: + blob: :activerecord.models.blob + name: Ім'я + record: :activerecord.models.record + record_type: Тип запису + active_storage/blob: + attachments: Вкладення + byte_size: Розмір у байтах + checksum: Контрольна сума + content_type: Тип контенту + filename: Ім'я файлу + key: Ключ + metadata: Метадані + preview_image_attachment: :activerecord.models.preview_image_attachment + preview_image_blob: :activerecord.models.preview_image_blob + service_name: Ім'я сервісу + variant_records: Записи варіантів + active_storage/variant_record: + blob: :activerecord.models.blob + image_attachment: :activerecord.models.image_attachment + image_blob: :activerecord.models.image_blob + variation_digest: Дайджест варіації + application_notifier: + notifications: Сповіщення + params: Параметри + record: :activerecord.models.record + record_type: Тип запису + application_notifier/notification: + event: :activerecord.models.event + read_at: Прочитано о + recipient: :activerecord.models.recipient + recipient_type: Тип одержувача + seen_at: Переглянуто о + better_together/community: + creator: :activerecord.models.creator + host: Хост + identifier: Ідентифікатор + lock_version: Версія блокування + person_community_memberships: Членство осіб у спільноті + person_members: Учасники особи + person_roles: Ролі особи + privacy: Конфіденційність + protected: Захищено + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/content/block: + accessibility_attributes: Атрибути доступності + content_settings: Налаштування контенту + css_settings: Налаштування CSS + data_attributes: Атрибути даних + html_attributes: HTML атрибути + identifier: Ідентифікатор + layout_settings: Налаштування макета + lock_version: Версія блокування + media_settings: Налаштування медіа + page_blocks: Блоки сторінки + pages: Сторінки + better_together/content/image: + accessibility_attributes: Атрибути доступності + content_settings: Налаштування контенту + css_settings: Налаштування CSS + data_attributes: Атрибути даних + html_attributes: HTML атрибути + identifier: Ідентифікатор + layout_settings: Налаштування макета + lock_version: Версія блокування + media_attachment: :activerecord.models.media_attachment + media_blob: :activerecord.models.media_blob + media_settings: Налаштування медіа + page_blocks: Блоки сторінки + pages: Сторінки + string_translations: Переклади рядків + better_together/content/page_block: + block: :activerecord.models.block + lock_version: Версія блокування + page: :activerecord.models.page + position: Позиція + better_together/content/rich_text: + accessibility_attributes: Атрибути доступності + content_settings: Налаштування контенту + css_settings: Налаштування CSS + data_attributes: Атрибути даних + html_attributes: HTML атрибути + identifier: Ідентифікатор + layout_settings: Налаштування макета + lock_version: Версія блокування + media_settings: Налаштування медіа + page_blocks: Блоки сторінки + pages: Сторінки + rich_text_content: :activerecord.models.rich_text_content + rich_text_content_en: :activerecord.models.rich_text_content_en + rich_text_content_es: :activerecord.models.rich_text_content_es + rich_text_content_fr: :activerecord.models.rich_text_content_fr + rich_text_translations: Переклади багатого тексту + better_together/conversation: + conversation_participants: Учасники розмови + creator: :activerecord.models.creator + lock_version: Версія блокування + messages: Повідомлення + participants: Учасники + title: Заголовок розмови + better_together/conversation_participant: + conversation: :activerecord.models.conversation + lock_version: Версія блокування + person: :activerecord.models.person + better_together/geography/continent: + community: :activerecord.models.community + countries: Країни + country_continents: Континенти країн + identifier: Ідентифікатор + lock_version: Версія блокування + protected: Захищено + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/geography/country: + community: :activerecord.models.community + continents: Континенти + country_continents: Континенти країн + identifier: Ідентифікатор + iso_code: ISO код + lock_version: Версія блокування + protected: Захищено + slug: Слаг + slugs: Слаги + states: Штати + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/geography/country_continent: + continent: :activerecord.models.continent + country: :activerecord.models.country + lock_version: Версія блокування + better_together/geography/region: + community: :activerecord.models.community + country: :activerecord.models.country + identifier: Ідентифікатор + lock_version: Версія блокування + protected: Захищено + region_settlements: Поселення регіону + settlements: Поселення + slug: Слаг + slugs: Слаги + state: :activerecord.models.state + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/geography/region_settlement: + lock_version: Версія блокування + protected: Захищено + region: :activerecord.models.region + settlement: :activerecord.models.settlement + better_together/geography/settlement: + community: :activerecord.models.community + country: :activerecord.models.country + identifier: Ідентифікатор + lock_version: Версія блокування + protected: Захищено + region_settlements: Поселення регіону + regions: Регіони + slug: Слаг + slugs: Слаги + state: :activerecord.models.state + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/geography/state: + community: :activerecord.models.community + country: :activerecord.models.country + identifier: Ідентифікатор + iso_code: ISO код + lock_version: Версія блокування + protected: Захищено + regions: Регіони + settlements: Поселення + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/identification: + active: Активний + agent: :activerecord.models.agent + agent_type: Тип агента + identity: :activerecord.models.identity + identity_type: Тип ідентичності + lock_version: Версія блокування + better_together/jwt_denylist: + exp: Закінчення + jti: JWT ідентифікатор + lock_version: Версія блокування + better_together/message: + content: Зміст + conversation: :activerecord.models.conversation + lock_version: Версія блокування + sender: :activerecord.models.sender + better_together/navigation_area: + identifier: Ідентифікатор + lock_version: Версія блокування + name: Назва + navigable: :activerecord.models.navigable + navigable_type: Тип навігації + navigation_items: Елементи навігації + protected: Захищено + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + style: Стиль + visible: Видимий + better_together/navigation_item: + children: Дочірні елементи + icon: Іконка + identifier: Ідентифікатор + item_type: Тип елемента + linkable: :activerecord.models.linkable + linkable_type: Тип посилання + lock_version: Версія блокування + navigation_area: :activerecord.models.navigation_area + parent: :activerecord.models.parent + position: Позиція + protected: Захищено + route_name: Назва маршруту + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + url: URL-адреса + visible: Видимий + better_together/new_message_notifier: + notifications: Сповіщення + params: Параметри + record: :activerecord.models.record + record_type: Тип запису + better_together/new_message_notifier/notification: + event: :activerecord.models.event + read_at: Прочитано о + recipient: :activerecord.models.recipient + recipient_type: Тип одержувача + seen_at: Переглянуто о + better_together/page: + blocks: Блоки + identifier: Ідентифікатор + image_blocks: Блоки зображень + keywords: Ключові слова + language: Мова + layout: Макет + lock_version: Версія блокування + meta_description: Мета-опис + page_blocks: Блоки сторінки + privacy: Конфіденційність + protected: Захищено + published: Опубліковано + published_at: Опубліковано о + rich_text_blocks: Блоки багатого тексту + rich_text_content: :activerecord.models.rich_text_content + rich_text_content_en: :activerecord.models.rich_text_content_en + rich_text_content_es: :activerecord.models.rich_text_content_es + rich_text_content_fr: :activerecord.models.rich_text_content_fr + rich_text_translations: Переклади багатого тексту + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + template: Шаблон + better_together/page_authorship_notifier: + notifications: Сповіщення + params: Параметри + record: :activerecord.models.record + record_type: Тип запису + better_together/page_authorship_notifier/notification: + event: :activerecord.models.event + read_at: Прочитано о + recipient: :activerecord.models.recipient + recipient_type: Тип одержувача + seen_at: Переглянуто о + better_together/person: + authorships: Авторство + community: :activerecord.models.community + community_roles: Ролі в спільноті + conversation_participants: Учасники розмови + conversations: Розмови + created_conversations: Створені розмови + description: Опис + identifications: Ідентифікації + identifier: Ідентифікатор + locale: Мова + lock_version: Версія блокування + member_communities: Членські спільноти + member_platforms: Членські платформи + name: Ім'я + notification_mentions: Згадки в сповіщеннях + notifications: Сповіщення + notify_by_email: Отримувати сповіщення електронною поштою + person_community_memberships: Членство особи в спільноті + person_platform_memberships: Членство особи в платформі + platform_roles: Ролі на платформі + preferences: Налаштування + show_conversation_details: Показати деталі розмови + slug: Ім'я користувача + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + user: :activerecord.models.user + user_identification: :activerecord.models.user_identification + better_together/person_community_membership: + joinable: :activerecord.models.joinable + lock_version: Версія блокування + member: :activerecord.models.member + role: :activerecord.models.role + better_together/person_platform_membership: + joinable: :activerecord.models.joinable + lock_version: Версія блокування + member: :activerecord.models.member + role: :activerecord.models.role + better_together/platform: + community: :activerecord.models.community + host: Хост + identifier: Ідентифікатор + invitations: Запрошення + lock_version: Версія блокування + person_members: Учасники + person_platform_memberships: Членство осіб на платформі + person_roles: Ролі осіб + privacy: Конфіденційність + protected: Захищено + settings: Налаштування + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + time_zone: Часовий пояс + url: URL-адреса + better_together/platform_invitation: + accepted_at: Прийнято + community_role: :activerecord.models.community_role + created_at: Створено + invitable: :activerecord.models.invitable + invitee: :activerecord.models.invitee + invitee_email: Електронна пошта запрошеного + inviter: :activerecord.models.inviter + last_sent: Останнє відправлення + locale: Мова + lock_version: Версія блокування + platform_role: :activerecord.models.platform_role + session_duration_mins: Тривалість сесії (хв) + status: Статус + token: Токен + type: Тип + valid_from: Дійсний з + valid_until: Дійсний до + better_together/resource_permission: + action: Дія + identifier: Ідентифікатор + lock_version: Версія блокування + position: Позиція + protected: Захищено + resource_type: Тип ресурсу + role_resource_permissions: Дозволи ролей для ресурсів + roles: Ролі + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + target: Ціль + better_together/role: + identifier: Ідентифікатор + lock_version: Версія блокування + position: Позиція + protected: Захищено + resource_permissions: Дозволи ресурсів + resource_type: Тип ресурсу + role_resource_permissions: Дозволи ролей для ресурсів + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + text_translations: Переклади тексту + better_together/role_resource_permission: + lock_version: Версія блокування + resource_permission: :activerecord.models.resource_permission + role: :activerecord.models.role + better_together/user: + confirmation_sent_at: Підтвердження надіслано о + confirmation_token: Токен підтвердження + confirmed_at: Підтверджено о + current_sign_in_at: Поточний вхід о + current_sign_in_ip: IP поточного входу + email: Електронна пошта + encrypted_password: Зашифрований пароль + failed_attempts: Невдалі спроби + last_sign_in_at: Останній вхід о + last_sign_in_ip: IP останнього входу + lock_version: Версія блокування + locked_at: Заблоковано о + password: Пароль + person: :activerecord.models.person + person_identification: :activerecord.models.person_identification + remember_created_at: Запам'ятати створено о + reset_password_sent_at: Скидання пароля надіслано о + reset_password_token: Токен скидання пароля + slugs: Слаги + string_translations: Переклади рядків + unconfirmed_email: Непідтверджена електронна пошта + unlock_token: Токен розблокування + better_together/wizard: + current_completions: Поточні завершення + first_completed_at: Вперше завершено о + identifier: Ідентифікатор + last_completed_at: Останнє завершено о + lock_version: Версія блокування + max_completions: Максимальні завершення + protected: Захищено + slug: Слаг + slugs: Слаги + string_translations: Переклади рядків + success_message: Повідомлення про успіх + success_path: Шлях успіху + text_translations: Переклади тексту + wizard_step_definitions: Визначення кроків майстра + wizard_steps: Кроки майстра + better_together/wizard_step: + completed: Завершено + creator: :activerecord.models.creator + identifier: Ідентифікатор + lock_version: Версія блокування + step_number: Номер кроку + wizard: :activerecord.models.wizard + wizard_step_definition: :activerecord.models.wizard_step_definition + better_together/wizard_step_definition: + form_class: Клас форми + identifier: Ідентифікатор + lock_version: Версія блокування + message: Повідомлення + protected: Захищено + slug: Слаг + slugs: Слаги + step_number: Номер кроку + string_translations: Переклади рядків + template: Шаблон + text_translations: Переклади тексту + wizard: :activerecord.models.wizard + wizard_steps: Кроки майстра + friendly_id/slug: + locale: Locale + lock_version: Lock version + scope: Scope + slug: Slug + sluggable: :activerecord.models.sluggable + sluggable_type: Sluggable type + mobility/backends/action_text/plain_text_translation: + body: Body + locale: Locale + name: Name + record: :activerecord.models.record + record_type: Record type + mobility/backends/action_text/rich_text_translation: + body: Body + embeds_attachments: Embeds attachments + embeds_blobs: Embeds blobs + locale: Locale + name: Name + record: :activerecord.models.record + record_type: Record type + mobility/backends/active_record/key_value/string_translation: + key: Key + locale: Locale + translatable: :activerecord.models.translatable + translatable_type: Translatable type + value: Value + mobility/backends/active_record/key_value/text_translation: + key: Key + locale: Locale + translatable: :activerecord.models.translatable + translatable_type: Translatable type + value: Value + noticed/event: + notifications: Notifications + params: Params + record: :activerecord.models.record + record_type: Record type + noticed/notification: + event: :activerecord.models.event + read_at: Read at + recipient: :activerecord.models.recipient + recipient_type: Recipient type + seen_at: Seen at + errors: + messages: + record_invalid: 'Validation failed: %{errors}' + restrict_dependent_destroy: + has_many: Cannot delete record because dependent %{record} exist + has_one: Cannot delete record because a dependent %{record} exists + models: + action_text/encrypted_rich_text: зашифрований багатий текст + action_text/rich_text: багатий текст + active_storage/attachment: вкладення + active_storage/blob: блоб + active_storage/variant_record: запис варіанта + application_notifier: Сповіщувач програми + application_notifier/notification: Сповіщення + better_together/community: Спільнота + better_together/content/block: Блок + better_together/content/css: Користувацький CSS + better_together/content/hero: Герой + better_together/content/html: HTML + better_together/content/image: Зображення + better_together/content/page_block: Блок сторінки + better_together/content/rich_text: Багатий текст + better_together/content/template: Файл шаблону + better_together/conversation: Розмова + better_together/conversation_participant: Учасник розмови + better_together/geography/continent: Континент + better_together/geography/country: Країна + better_together/geography/country_continent: Континент країни + better_together/geography/region: Регіон + better_together/geography/region_settlement: Поселення регіону + better_together/geography/settlement: Поселення + better_together/geography/state: Штат + better_together/guest_access: Гостьовий доступ + better_together/identification: Ідентифікація + better_together/infrastructure/building: Будівля + better_together/joatu/category: Категорія пропозицій та запитів + better_together/jwt_denylist: JWT чорний список + better_together/message: Повідомлення + better_together/navigation_area: Область навігації + better_together/navigation_item: Елемент навігації + better_together/new_message_notifier: Сповіщувач нових повідомлень + better_together/new_message_notifier/notification: сповіщувач нових повідомлень/сповіщення + better_together/page: Сторінка + better_together/person: Особа + better_together/person_community_membership: Членство особи в спільноті + better_together/person_platform_membership: Членство особи в платформі + better_together/platform: Платформа + better_together/platform_invitation: Запрошення платформи + better_together/resource_permission: Дозвіл ресурсу + better_together/role: Роль + better_together/role_resource_permission: Дозвіл ролі ресурсу + better_together/user: Користувач + better_together/wizard: Майстер + better_together/wizard_step: Крок майстра + better_together/wizard_step_definition: Визначення кроку майстра + friendly_id/slug: Слаг + mobility/backends/action_text/plain_text_translation: переклад звичайного тексту + mobility/backends/action_text/rich_text_translation: переклад багатого тексту + mobility/backends/active_record/key_value/string_translation: переклад рядка + mobility/backends/active_record/key_value/text_translation: переклад тексту + noticed/event: подія + noticed/notification: сповіщення + attributes: + privacy_list: + community: Спільнота + private: Приватний + public: Публічний + better_together: + addresses: + add: Додати адресу + address: Адреса + city_name: Місто + country_name: Країна + label: Мітка + labels: + billing: Рахунок + home: Дім + mailing: Поштова + main: Основна + other: Інша + physical: Фізична + shipping: Доставка + work: Робота + line1: Адреса рядок 1 + line2: Адреса рядок 2 + physical: Фізична + postal: Поштова + postal_code: Поштовий індекс + state_province_name: Штат/Провінція + title: Адреси + agreement_terms: + add: Додати + title: Умови угоди + agreements: + form: + page_help: Якщо встановлено, буде відображено вміст пов'язаної сторінки замість + умов угоди. + page_label: Пов'язана сторінка (необов'язково) + no_terms: Умови не визначено. + notice: + body: Будь ласка, перегляньте повну угоду перед прийняттям. + close: Закрити + title: Переглянути обов'язкову угоду + participant: + pending: очікує + show: + title: Угода + terms: Умови + view: Переглянути + authorship_mailer: + authorship_changed_notification: + greeting: Привіт %{recipient_name}, + intro_added: Вас додано як автора до сторінки %{page}. + intro_added_by: "%{actor_name} додав вас як автора до сторінки %{page}." + intro_removed: Вас видалено як автора зі сторінки %{page}. + intro_removed_by: "%{actor_name} видалив вас як автора зі сторінки %{page}." + signature_html: "— Команда %{platform}" + subject_added: Вас додано як автора до %{page} + subject_added_by: "%{actor_name} додав вас як автора до %{page}" + subject_removed: Ваше авторство видалено з %{page} + subject_removed_by: "%{actor_name} видалив ваше авторство з %{page}" + view_page: Переглянути сторінку + view_page_link: Натисніть тут, щоб переглянути сторінку + buildings: + add: Додати + title: Будівлі + calendars: + default_description: Календар за замовчуванням для %s + personal_calendar_name: Особистий календар %{name} + calls_for_interest: + back_to_calls_for_interest: Повернутися до закликів до зацікавленості + hints: + description: Опис + ends_at: Закінчується о + name: Ім'я + slug: Слаг + starts_at: Починається о + labels: + ends_at: Закінчується о + privacy: Конфіденційність + starts_at: Починається о + none-yet: Поки що немає + save_call_for_interest: Зберегти заклик до зацікавленості + tabs: + details: Деталі + images: Зображення + time-and-place: Час і місце + view_call_for_interest: Переглянути заклик до зацікавленості + categories: + back_to_categories: Назад до категорій + index: + new_btn_text: Нова категорія + save_category: Зберегти категорію + view_category: Переглянути категорію + checklist_item: + update_failed: Помилка при оновленні елемента списку. + updated: Елемент списку оновлено. + checklist_items: + created: Елемент створено + description: Опис + edit_title: Редагувати елемент чек-листа + form_hint: Надайте коротку мітку та необов'язкові деталі. Використовуйте конфіденційність + для контролю хто бачить цей елемент. + hint_description: Необов'язкові деталі або інструкції для цього елемента. Підтримує + форматування багатого тексту. + hint_label: Короткий заголовок для елемента чек-листа (обов'язково). + hint_parent: 'Необов''язково: вкласти цей елемент під інший. Максимальна глибина + вкладення становить 2.' + hint_privacy: 'Виберіть, хто може бачити цей елемент: Публічний або Приватний.' + label: Мітка + move_down: Перемістити вниз + move_up: Перемістити вгору + new_title: Новий елемент чек-листа + parent: Батьківський елемент (необов'язково) + reorder: Переупорядкувати елемент + sign_in_to_toggle: Увійдіть, щоб відмітити цей елемент як виконаний + untitled: Елемент без назви + checklists: + edit: + title: Редагувати чек-лист + index: + new: Новий чек-лист + title: Чек-листи + new: + title: Новий чек-лист + show: + completed_message: Чек-лист завершено + items_title: Елементи + no_items: Немає елементів для відображення + communities: + index: + new_btn_text: Нова спільнота + new: + title: Спільноти + none-yet: Поки що немає + show: + create_event: Створити подію + tabs: + buildings: Будівлі + contact_details: Контактні деталі + details: Деталі + images: Зображення + contact_details: + contact_information: Контактна інформація + title: Контактні деталі + contactable: + addresses: Адреси + contact: Контакт + contacts: Контакти + email_addresses: Адреси електронної пошти + phone_numbers: Номери телефонів + social_media_accounts: Акаунти соціальних мереж + website_links: Посилання на веб-сайти + contacts: + add: Add Contact + content: + blocks: + associated_pages: Associated Pages + conversation_mailer: + new_message_notification: + from_address: Нове повідомлення через %{from_address} + from_address_with_sender: "%{sender_name} через %{from_address}" + greeting: Привіт %{recipient_name}, + message_intro: У вас є непрочитане повідомлення + message_intro_with_sender: У вас є непрочитане повідомлення від %{sender_name} + signature: Підпис + signature_html: |- + З найкращими побажаннями,
+ Команда %{platform} + subject: У вашій розмові є непрочитане повідомлення + subject_with_title: "[%{conversation}] розмова має непрочитане повідомлення" + view_conversation: 'Ви можете переглянути та відповісти на це повідомлення, + натиснувши посилання нижче:' + view_conversation_link: Перейти до розмови + conversations: + communicator: + active_conversations: Активні розмови + add_participants: Додати учасників + conversations: Розмови + create_conversation: Створити розмову + last_message: Останнє повідомлення + new: Нова + new_conversation: Нова розмова + conversation: + last_message: Останнє повідомлення + left: Ви покинули розмову %{conversation} + conversation_content: + edit_conversation: Редагувати розмову + leave_conversation: Покинути розмову + options_tooltip: Параметри розмови + empty: + no_messages: Повідомлень поки що немає. Чому б не почати розмову? + errors: + no_permitted_participants: Ви можете додавати лише менеджерів платформи або + учасників, які дали згоду на отримання повідомлень. + form: + add_participants: Додати учасників + create_conversation: Створити розмову + first_message: Перше повідомлення + first_message_hint: Напишіть відкриваюче повідомлення для розмови + participants_hint: Виберіть одну або кількох людей для включення до розмови + title_hint: Необов'язково. Короткий описовий заголовок може допомогти учасникам + знайти цю розмову пізніше + index: + conversations: Розмови + new: Нова + layout_with_sidebar: + active_conversations: Активні розмови + add_participants: Додати учасників + conversations: Розмови + create_conversation: Створити розмову + last_message: Останнє повідомлення + new: Нова + new_conversation: Нова розмова + list: + add_participants: Додати учасників + conversations: Розмови + create_conversation: Створити розмову + new: Нова + new_conversation: Нова розмова + new: + add_participants: Додати учасників + create_conversation: Створити розмову + new_conversation: Нова розмова + sidebar: + conversations: Розмови + new: Нова + device_permissions: + location: + denied: Відхилено + enabled: Увімкнено + unsupported: Не підтримується + status: + denied: Відхилено + granted: Надано + unknown: Невідомо + email_addresses: + add: Додати адресу електронної пошти + email: Електронна пошта + label: Мітка + labels: + other: Інша + personal: Особиста + school: Школа + work: Робота + title: Адреси електронної пошти + event_invitations_mailer: + invite: + event_details: Деталі події + greeting: Привіт, + invited_by_html: Вас запросив %{inviter_name}. + invited_html: Вас запрошено на подію %{event_name}. + need_account_html: Вам потрібно створити обліковий запис, щоб прийняти це + запрошення та приєднатися до події. + review_invitation: Переглянути запрошення + subject: Вас запрошують на подію + when: Коли + where: Де + event_mailer: + event_reminder: + description: Опис + duration: Тривалість + ends_at: Закінчується о + greeting: Привіт %{recipient_name}, + hours: + few: "%{count} години" + many: "%{count} годин" + one: "%{count} година" + other: "%{count} годин" + location: Місце + register_link: Зареєструватися або RSVP + reminder_message: Це дружнє нагадування про майбутню подію "%{event_name}". + signature_html: |- + З найкращими побажаннями,
+ Команда %{platform} + subject: 'Нагадування: %{event_name}' + time_tbd: Час буде оголошено + view_event_link_html: 'Переглянути деталі події: %{link}' + when: Коли + event_update: + changes_made: Changes made + current_details: Current details + description: Description + duration: Duration + ends_at: Ends at + greeting: Привіт %{recipient_name}, + hours: + few: "%{count} години" + many: "%{count} годин" + one: "%{count} година" + other: "%{count} годин" + location: Місце + register_link: Register or RSVP + signature_html: |- + Best regards,
+ The %{platform} Team + subject: 'Event updated: %{event_name}' + time_tbd: Time tbd + update_message_plural: The event "%{event_name}" has been updated with several + changes. + update_message_singular: The event "%{event_name}" has been updated. + view_event_link_html: 'View event details: %{link}' + when: When + events: + actions: + create_new_address: Create new address + create_new_building: Create new building + create_new_short: New + add_to_calendar: Add to calendar (.ics) + attendees: Attendees + back_to_events: View Events + greater_than_or_equal_to: must be greater than or equal to %{count} + hints: + create_permission_denied: Дозвіл на створення відхилено + description: Опис + duration_minutes: Скільки часу триватиме подія. Змінюється автоматично при + зміні часу початку або закінчення. + ends_at: Закінчується о + location: Місце + location_name: Назва місця + name: Назва + select_address: Вибрати адресу + select_building: Вибрати будівлю + slug: Слаг + starts_at: Починається о + hosted_by: Організатор + ics: + view_details_url: URL деталей перегляду + in: має бути в %{count} + inclusion: не включено до списку + invalid: недійсний + labels: + duration_minutes: Тривалість + ends_at: Закінчується о + location: Місце + location_name: Назва місця + location_type: Тип місця + privacy: Конфіденційність + select_address: Вибрати адресу + select_building: Вибрати будівлю + starts_at: Починається о + less_than: має бути менше %{count} + less_than_or_equal_to: має бути менше або дорівнювати %{count} + location_types: + address: Адреса + building: Будівля + simple: Простий + login_required: Будь ласка, увійдіть для управління RSVP. + model_invalid: 'Помилка валідації: %{errors}' + no_attendees: Учасників поки що немає. + no_description_available: Опис недоступний. + none-yet: Поки що немає + not_a_number: не є числом + not_an_integer: має бути цілим числом + not_found: не знайдено + not_locked: не було заблоковано + not_saved: + few: 'Виникло %{count} помилки під час збереження %{resource}:' + many: 'Виникло %{count} помилок під час збереження %{resource}:' + one: 'Виникла помилка під час збереження %{resource}:' + other: 'Виникло %{count} помилок під час збереження %{resource}:' + odd: має бути непарним + other_than: має бути іншим ніж %{count} + placeholders: + location_name: Назва місця + present: має бути порожнім + prompts: + select_address: Вибрати адресу + select_building: Вибрати будівлю + register: Зареєструватися + relationship: + calendar: Подія календаря + created: Створено вами + going: Ви йдете + interested: Ви зацікавлені + required: має існувати + rsvp_cancel: Скасувати RSVP + rsvp_cancelled: RSVP скасовано + rsvp_counts: 'Йдуть: %{going} · Зацікавлені: %{interested}' + rsvp_going: Йду + rsvp_interested: Зацікавлений + rsvp_not_available: RSVP недоступне для цієї події. + rsvp_saved: RSVP збережено + rsvp_unavailable_draft: RSVP буде доступним після планування цієї події. + save_event: Зберегти подію + tabs: + details: Деталі + images: Зображення + time-and-place: Час і місце + taken: вже зайнято + time: + hour: година + hours: годин + minutes: хвилин + too_long: + few: занадто довгий (максимум %{count} символи) + many: занадто довгий (максимум %{count} символів) + one: занадто довгий (максимум %{count} символ) + other: занадто довгий (максимум %{count} символів) + too_short: + few: занадто короткий (мінімум %{count} символи) + many: занадто короткий (мінімум %{count} символів) + one: занадто короткий (мінімум %{count} символ) + other: занадто короткий (мінімум %{count} символів) + units: + minutes: minutes + view_event: View event + wrong_length: + few: неправильна довжина (точно %{count} символи) + many: неправильна довжина (точно %{count} символів) + one: неправильна довжина (точно %{count} символ) + other: неправильна довжина (точно %{count} символів) + geography: + locatable_location: + errors: + no_location_source: Must specify either a name or location + regions: + index: + title: Regions + settlements: + index: + title: Settlements + global: + add: Add + hub: + activities: + back_to_hub: Back to Hub + page_title: All Activity + index: + activity: Activity + announcements: Announcements + new_members: New members + page_title: Community Hub + platform_updates: Platform Updates + recommended_communities: Recommended Communities + trending_topics: Trending topics + upcoming_events: Upcoming Events + view_all_activity: View all + recent_offers_card: + new_offer: New Offer + no_offers: No recent offers + recent_offers: Recent Offers + view_all_offers: View all + recent_requests_card: + new_request: New Request + no_requests: No recent requests + recent_requests: Recent Requests + view_all_requests: View all + suggested_matches_card: + for_your_latest_offer: For your latest offer + for_your_latest_request: For your latest request + no_suggestions: No suggestions yet — create an offer or request to see matches. + suggested_matches: Suggested Matches + infrastructure: + locations: Locations + invitations: + accept: Accept + confirm_remove: Are you sure you want to remove this invitation? + decline: Decline + event_name: 'Event:' + event_panel: + title: Invite People + external_user: External user + invite_by_email: Invite by Email + invite_person: Invite Member + invitee: Invitee + invitee_email: Email + invitee_type: Invitee Type + login_to_respond: Please log in to respond to your invitation. + pending: Pending Invitations + register_to_respond: Please register to respond to your invitation. + review: Invitation + search_people: Search for people... + select_person: Select Person + send_invite: Send Invitation + title: Event Invitations + type: + email: Email + person: Member + joatu: + agreements: + accept: Accept + create: + success: Success + index: + title: Agreements + offer: Offer + offer_creator: Offer Creator + participants: Participants + reject: Reject + request: Request + request_creator: Request Creator + show: + details: Details + title: Agreement + status: Status + by: by + categories: + index: + new_btn_text: New Category + help: + agreements: + index: Agreements connect one Offer and one Request. Use this list to review + or manage your agreements. + show: This page connects one Offer and one Request. If you are involved, + you can accept or reject to confirm the plan. + hide: Hide this help + offers: + form: Tell people what you can offer. A clear title and a few details go + a long way. Choose one or more categories so the right people can find + you. + index: Browse offers. Use search and filters to find what you need. Click + an offer to see details. + show: This is an offer. If it fits your needs, you can start a conversation + or create an agreement from a matching request. + requests: + form: Tell people what you need. A clear title and a few details help others + understand. Pick one or more categories so helpers can find you. + index: Browse requests. Use search and filters to find ways to help. Click + a request to see details. + show: This is a request. If it matches your offer, you can start a conversation + or create an agreement from a matching offer. + screenshot_alt: Help screenshot + show_again: Show help again + hub: + empty_agreements: No agreements yet. + empty_offers: No offers yet. + empty_requests: No requests yet. + my_agreements: My Agreements + my_offers: My Offers + my_requests: My Requests + sign_in_hint: Sign in to see your offers, requests, and agreements. + suggested: Suggested Matches + title: Exchange Hub + view_all_agreements: View all agreements + view_all_offers: View all offers + view_all_requests: View all requests + matches: + create_agreement: Create Agreement + matches_offer: 'Matches offer:' + matches_request: 'Matches request:' + none: No matches found right now. + title: Potential Matches + nav: + agreements: Agreements + hub: Hub + manage_categories: Manage Categories + offers: Offers + requests: Requests + offers: + agreement_heading: Agreement + agreement_with: Agreement with + back_to_offers: Back to Offers + edit_offer: Edit Offer + empty: No offers found + index_header: Offers + new_offer: New Offer + response_to_your_offer_heading: Response to your Offer + save_offer: Save Offer + source_request_heading: Responding to Request + view_agreement: View Agreement + view_offer: View Offer + your_response_heading: Your Response + requests: + agreement_heading: Agreement + back_to_requests: Back to Requests + edit_request: Edit Request + empty: No requests found + index_header: Requests + new_request: New Request + respond_with_offer: Respond with Offer + response_to_your_request_heading: Response to your Request + save_request: Save Request + source_offer_heading: Responding to Offer + view_agreement: View Agreement + view_request: View Request + your_response_heading: Your Response + respond_with_request: Respond with Request + response_to_your: Response to your %{type} + responses: + in_response_to: In response to + search: + labels: + filter_by_type: Filter by category + order_by: Order by + per_page: Per page + search-term: Search term + status: Status + submit: Search + newest: Newest + oldest: Oldest + placeholders: + search: Search by title or description + title: Search + status: + any: Any + closed: Closed + open: Open + type: + offer: Offer + request: Request + you: You + joatu_mailer: + agreement_created: + greeting: Hello %{recipient_name}, + message_intro: An agreement has been created between "%{offer_name}" and "%{request_name}". + signature: |- + Best regards, + The %{platform} Team + signature_html: |- + Best regards,
+ The %{platform} Team + subject: A new agreement was created + labelable: + custom-label-placeholder: Your custom label + loading: Loading... + mailer: + footer: + no_reply: This is an automated message. Please do not reply to this email. + messages: + form: + placeholder: Type your message... + send: Send + metrics: + shares: + invalid_parameters: Invalid parameters for share tracking. + shares_controller: + error_tracking: 'Error tracking share:' + failed_tracking: Failed to track share. + ga_not_initialized: Google Analytics not initialized. + unsupported_platform: 'Unsupported platform: %{platform}' + mobile_bar: + conversations: Conversations + navigation: Navigation + profile: Profile + models: + address_missing_type: Address must be physical, postal, or both. + ends_at_before_starts_at: must be after the start time + host_single: can only be set for a single record + protected_destroy: This record is protected and cannot be deleted. + navigation_areas: + edit: + title: Edit Navigation Area + index: + items: Items + new_navigation_area: New navigation area + new: + title: New Navigation Area + show: + new_navigation_item: New navigation item + navigation_items: + edit: + title: 'Edit Navigation Item: %{title}' + form: + dynamic_route: Dynamic Route + enter_nav_item_title: Enter the nav item title. + link_to_page: Link to Page + parent_item: Parent Item + index: + new_navigation_item: New Navigation Item + title: Navigation Items for %{name} + new: + title: New Navigation Item for %{name} + route_names: + calendars: Calendars + calls_for_interest: Calls for Interest + communities: Communities + content_blocks: Content Blocks + events: Events + geography_continents: Continents + geography_countries: Countries + geography_maps: Maps + geography_regions: Regions + geography_settlements: Settlements + geography_states: States + host_dashboard: Host Dashboard + joatu_offers: Offers + joatu_requests: Requests + metrics_reports: Metrics Reports + navigation_areas: Navigation Areas + pages: Pages + people: People + platforms: Platforms + resource_permissions: Resource Permissions + roles: Roles + users: Users + new_message_notifier: + from_html: 'From: %{sender}' + new_message: New Message + not_found: + description: The page you are looking for might have been removed, had its name + changed, or is temporarily unavailable. + title: 404 - Page Not Found + notifications: + event_invitation: + body: Invitation to %{event_name} + title: You have been invited to an event + event_reminder: + body_1h: "%{event_name} starts in 1 hour at %{starts_at}" + body_24h: "%{event_name} starts tomorrow at %{starts_at}" + body_generic: 'Reminder: %{event_name} starts at %{starts_at}' + title: 'Reminder: %{event_name}' + event_update: + body: "%{event_name} has been updated: %{changes}" + title: 'Event updated: %{event_name}' + index: + no_notifications: You have no notifications. + notifications: Notifications + joatu: + agreement_created: + content: An agreement between "%{offer}" and "%{request}" has been created. + title: Agreement created + mark_all_as_read: Mark all as read + mark_as_read: Mark as read + new: New + new_message: + content: "%{content}" + title: "%{sender}: %{conversation}" + no_notifications: No notifications + time_ago: "%{time} ago" + page_authorship_notifier: + added: You were added as an author on %{page_title} + added_by: "%{actor_name} added you as an author on %{page_title}" + removed: You were removed as an author on %{page_title} + removed_by: "%{actor_name} removed you as an author on %{page_title}" + pages: + form: + create_page_before_adding_content: Create page before adding content + index: + clear_filter: Clear filter + clear_filters: Clear Filters + filter: Filter + new_page: New page + search_by_slug: Search by slug... + search_by_title: Search by title... + people: + allow_messages_from_members: Allow messages from platform members + calendar: + attended: Attended + no_events: No events in calendar + no_events_description: Events will appear here when you RSVP as "Going" to + events. + no_past_events: No past events attended. + no_upcoming_events: No upcoming events. + recent_past_events: Recent Past Events + title: Personal Calendar + upcoming_events: Upcoming Events + device_permissions: + camera: Camera + location: Location + microphone: Microphone + notifications: Notifications + permissions_group_label: Permissions group label + index: + new_person: New person + notification_preferences: Message notification preferences + submit: + save: Save + tabs: + contact_details: Contact Details + details: Details + device-permissions: Device-permissions + images: Images + preferences: Preferences + person_block: + cannot_block_manager: no puede ser un administrador de la plataforma + cannot_block_self: no puedes bloquearte a ti mismo + person_blocks: + index: + actions: Actions + block_person: Block Person + blocked_at: Blocked + blocked_count: + few: Заблоковано %{count} особи + many: Заблоковано %{count} осіб + one: Заблоковано 1 особу + other: Заблоковано %{count} осіб + zero: Нікого не заблоковано + blocked_on: Blocked on %{date} + no_blocked_people: You haven't blocked anyone yet. + no_blocked_people_description: When you block someone, they won't be able + to interact with you. + search: Search + search_placeholder: Search blocked people... + title: Blocked People + unblock: Unblock + unblock_confirmation: Are you sure you want to unblock this person? + new: + back_to_list: Back to Blocked People + blocked_person_help: Select a person you want to block from the search results. + blocked_person_label: Person to Block + blocked_person_placeholder: Search for a person to block... + blocked_person_prompt: Select a person to block + cancel: Cancel + submit: Block Person + title: Block Person + notices: + already_blocked: This person is already blocked. + blocked: Person has been blocked successfully. + not_found: Person not found. + platform_manager_error: Platform managers cannot be blocked. + self_block_error: You cannot block yourself. + unblocked: Person has been unblocked successfully. + phone_numbers: + add: Add Phone Number + label: Label + labels: + fax: Fax + home: Home + mobile: Mobile + other: Other + work: Work + number: Номер + title: Номери телефонів + platform_invitation: + acceptance_html: Реєструючи обліковий запис користувача нижче, ви приймаєте + це запрошення приєднатися до %{platform}. + community_role_html: Вам надано роль %{role} в спільноті %{platform}. + greeting-placeholder: Було приємно познайомитися з вами нещодавно. Ось запрошення, + яке я обіцяв вам надіслати! + hints: + type: Виберіть тип запрошення. + platform_role_html: Вам надано роль %{role} на платформі %{platform}. + title: Вас запрошують приєднатися до %{platform} + platform_invitation_mailer: + invite: + greeting: Привіт %{recipient_name}, + ignore: Якщо ви не очікували цього запрошення, будь ласка, проігноруйте цей + електронний лист. + invitation_code: 'Код запрошення: %{invitation_code}' + link_text: Прийняти запрошення + message: 'Вас запрошено приєднатися до %{platform} на нашій платформі. Щоб + прийняти запрошення, будь ласка, натисніть посилання нижче:' + signature_html: З найкращими побажаннями,
Команда %{platform} + subject: Вас запрошують приєднатися до %{platform}! + valid_from: Це запрошення дійсне з %{valid_from}. + valid_period: Це запрошення дійсне з %{valid_from} до %{valid_until}. + platform_invitations: + create_new_invitation: Створити нове запрошення + filter_by_status: Фільтрувати за статусом + invitations: Запрошення + new_invitation: Нове запрошення + search_by_email: Пошук за електронною поштою + status: + accepted: Прийнято + expired: Закінчився термін + pending: Очікує + platforms: + show: + new_invitation: Нове запрошення + posts: + authors: Автори + back_to_posts: Назад до постів + create_post: Створити перший пост + created_on: Створено %{date} + created_on_short: Створено + delete: Видалити запис + edit: Редагувати пост + edit_post: Редагувати пост + index: + new_btn_text: Новий пост + labels: + privacy: Конфіденційність + new_post: Новий пост + none_body: Наразі немає постів для показу. + none_title: Постів поки що немає + published_on: Опубліковано %{date} + save_post: Зберегти пост + updated_on_short: Оновлено + view_post: Переглянути пост + primary: Основний + registrations: + captcha_validation_failed: Помилка перевірки безпеки. Будь ласка, спробуйте + знову. + remove: Видалити + resource_permissions: + index: + new_resource_permission: Новий дозвіл ресурсу + roles: + index: + new_role: Нова роль + search: + all: Все + page_title: Результати пошуку для "%{query}" + select: Вибрати + settings: + index: + account: + coming_soon: Налаштування облікового запису з'являться незабаром. + description: Оновіть свої облікові дані та налаштування безпеки. + title: Налаштування облікового запису + blocked_people: + description: Керуйте користувачами, яких ви заблокували від взаємодії з + вами. + title: Заблоковані люди + navigation_aria_label: Навігація налаштувань + personal: + coming_soon: Особисті налаштування з'являться незабаром. + description: Керуйте своєю особистою інформацією та налаштуваннями. + title: Особисті налаштування + platform: + coming_soon: Налаштування платформи з'являться незабаром. + description: Налаштуйте загальноплатформні параметри та налаштування. + title: Налаштування платформи + privacy: + coming_soon: Privacy settings are coming soon. + description: Control your privacy and data sharing preferences. + title: Privacy Settings + tabs: + account: Account + blocked_people: Blocked People + personal: Personal + platform: Platform + privacy: Privacy + title: Settings + share_buttons: + aria_label: Share on %{platform} + bluesky: Share on Bluesky + default_title: Check this out! + facebook: Share on Facebook + linkedin: Share on LinkedIn + pinterest: Pin + reddit: Share on Reddit + share: Share + whatsapp: Share on WhatsApp + shared: + translated_field_hints: + description: Short description (at least one locale must be filled) + description_html: Rich-text description (supports formatting; at least one + locale must be filled) + name: Enter a name (at least one locale must be filled) + slug: URL‑friendly identifier (unique across locales) + unpublished: Unpublished + social_media_accounts: + add: Додати акаунт соціальних мереж + handle: Нікнейм + platform: Платформа + title: Акаунти соціальних мереж + url: URL-адреса + website_links: + add: Додати посилання на веб-сайт + labels: + about_us: Про нас + blog: Блог + careers: Кар'єра + community_page: Сторінка спільноти + company_website: Веб-сайт компанії + contact_us: Зв'яжіться з нами + documentation: Документація + donations: Пожертви + events: Події + faq: ЧаПи + forum: Форум + newsletter: Розсилка + other: Інше + personal_website: Особистий веб-сайт + portfolio: Портфоліо + privacy_policy: Політика конфіденційності + product_page: Сторінка продукту + resume: Резюме + services: Послуги + support: Підтримка + terms_of_service: Умови використання + title: Посилання на веб-сайти + block: :activerecord.models.block + community: + create_failed: Створення не вдалося + created: Створено + update_failed: Оновлення не вдалося + updated: Оновлено + content: Зміст + conversation: :activerecord.models.conversation + conversation_participants: Учасники розмови + conversations: + errors: + no_permitted_participants: You can only add platform managers or members who + have opted in to receive messages. + date: + abbr_day_names: + - Нд + - Пн + - Вт + - Ср + - Чт + - Пт + - Сб + abbr_month_names: + - + - Січ + - Лют + - Бер + - Кві + - Тра + - Чер + - Лип + - Сер + - Вер + - Жов + - Лис + - Гру + day_names: + - Неділя + - Понеділок + - Вівторок + - Середа + - Четвер + - П'ятниця + - Субота + formats: + default: "%Y-%m-%d" + long: "%B %d, %Y" + short: "%b %d" + month_names: + - + - Січень + - Лютий + - Березень + - Квітень + - Травень + - Червень + - Липень + - Серпень + - Вересень + - Жовтень + - Листопад + - Грудень + order: + - :year + - :month + - :day + datetime: + distance_in_words: + about_x_hours: + few: близько %{count} годин + many: близько %{count} годин + one: близько години + other: близько %{count} годин + about_x_months: + few: близько %{count} місяців + many: близько %{count} місяців + one: близько місяця + other: близько %{count} місяців + about_x_years: + few: близько %{count} років + many: близько %{count} років + one: близько року + other: близько %{count} років + almost_x_years: + few: майже %{count} років + many: майже %{count} років + one: майже рік + other: майже %{count} років + half_a_minute: півхвилини + less_than_x_minutes: + few: менше %{count} хвилин + many: менше %{count} хвилин + one: менше хвилини + other: менше %{count} хвилин + less_than_x_seconds: + few: менше %{count} секунд + many: менше %{count} секунд + one: менше секунди + other: менше %{count} секунд + over_x_years: + few: понад %{count} років + many: понад %{count} років + one: понад рік + other: понад %{count} років + x_days: + few: "%{count} дні" + many: "%{count} днів" + one: день + other: "%{count} днів" + x_minutes: + few: "%{count} хвилини" + many: "%{count} хвилин" + one: хвилина + other: "%{count} хвилин" + x_months: + few: "%{count} місяці" + many: "%{count} місяців" + one: місяць + other: "%{count} місяців" + x_seconds: + few: "%{count} секунди" + many: "%{count} секунд" + one: секунда + other: "%{count} секунд" + x_years: + few: "%{count} роки" + many: "%{count} років" + one: рік + other: "%{count} років" + prompts: + day: День + hour: Година + minute: Хвилина + month: Місяць + second: Секунда + year: Рік + devise: + confirmations: + confirmed: Вашу адресу електронної пошти було успішно підтверджено. + new: + email: + label: Електронна пошта + resend_confirmation_instructions: Повторно надіслати інструкції підтвердження + send_instructions: Ви отримаєте електронний лист з інструкціями щодо підтвердження + вашої адреси електронної пошти через кілька хвилин. + send_paranoid_instructions: Якщо ваша адреса електронної пошти є в нашій базі + даних, ви отримаєте електронний лист з інструкціями щодо підтвердження вашої + адреси електронної пошти через кілька хвилин. + failure: + already_authenticated: Ви вже увійшли до системи. + inactive: Ваш обліковий запис ще не активовано. + invalid: Неправильний %{authentication_keys} або пароль. + last_attempt: У вас є ще одна спроба до блокування вашого облікового запису. + locked: Ваш обліковий запис заблоковано. + not_found_in_database: Неправильний %{authentication_keys} або пароль. + timeout: Ваша сесія закінчилася. Будь ласка, увійдіть знову, щоб продовжити. + unauthenticated: Вам потрібно увійти або зареєструватися, щоб продовжити. + unconfirmed: Вам потрібно підтвердити свою адресу електронної пошти, щоб продовжити. + mailer: + confirmation_instructions: + action: Підтвердити мій обліковий запис + greeting: Ласкаво просимо %{recipient}! + instruction: 'Ви можете підтвердити електронну пошту свого облікового запису + за посиланням нижче:' + subject: Інструкції підтвердження + email_changed: + greeting: Привіт %{recipient}! + message: Ми звертаємося до вас, щоб повідомити, що вашу електронну пошту було + змінено на %{email}. + message_unconfirmed: Ми звертаємося до вас, щоб повідомити, що вашу електронну + пошту змінюється на %{email}. + subject: Електронну пошту змінено + password_change: + greeting: Привіт %{recipient}! + message: Ми звертаємося до вас, щоб повідомити, що ваш пароль було змінено. + subject: Пароль змінено + reset_password_instructions: + action: Змінити мій пароль + greeting: Привіт %{recipient}! + instruction: Хтось запросив посилання для зміни вашого пароля. Ви можете зробити + це за посиланням нижче. + instruction_2: Якщо ви не запитували це, будь ласка, ігноруйте цей електронний + лист. + instruction_3: Ваш пароль не зміниться, поки ви не перейдете за посиланням + вище і не створите новий. + subject: Інструкції скидання пароля + unlock_instructions: + action: Розблокувати мій обліковий запис + greeting: Привіт %{recipient}! + instruction: 'Натисніть посилання нижче, щоб розблокувати свій обліковий запис:' + message: Ваш обліковий запис було заблоковано через надмірну кількість невдалих + спроб входу. + subject: Інструкції розблокування + omniauth_callbacks: + failure: Не вдалося автентифікувати вас з %{kind} через "%{reason}". + success: Успішно автентифіковано з облікового запису %{kind}. + passwords: + edit: + change_my_password: Змінити мій пароль + change_your_password: Змінити ваш пароль + confirm_new_password: Підтвердити новий пароль + new_password: Новий пароль + new: + email: + help: Введіть вашу адресу електронної пошти для отримання інструкцій зі + скидання. + label: Електронна пошта + forgot_your_password: Забули свій пароль? + send_me_reset_password_instructions: Надіслати мені інструкції зі скидання + пароля + no_token: Ви не можете отримати доступ до цієї сторінки, не перейшовши з електронного + листа для скидання пароля. Якщо ви перейшли з електронного листа для скидання + пароля, будь ласка, переконайтеся, що використали повну надану URL-адресу. + send_instructions: Ви отримаєте електронний лист з інструкціями щодо скидання + вашого пароля через кілька хвилин. + send_paranoid_instructions: Якщо ваша адреса електронної пошти є в нашій базі + даних, ви отримаєте посилання для відновлення пароля на вашу адресу електронної + пошти через кілька хвилин. + updated: Ваш пароль було успішно змінено. Тепер ви увійшли до системи. + updated_not_active: Ваш пароль було успішно змінено. + registrations: + destroyed: До побачення! Ваш обліковий запис було успішно скасовано. Сподіваємося + побачити вас знову найближчим часом. + edit: + are_you_sure: Ви впевнені? + cancel_my_account: Скасувати мій обліковий запис + currently_waiting_confirmation_for_email: 'Очікує підтвердження для: %{email}' + edit_again: Редагувати деталі облікового запису + leave_blank_if_you_don_t_want_to_change_it: залиште порожнім, якщо не хочете + змінювати + title: Редагувати %{resource} + unhappy: Незадоволені? + update: Оновити + we_need_your_current_password_to_confirm_your_changes: нам потрібен ваш поточний + пароль для підтвердження змін + new: + agreements: + privacy_policy: Я погоджуюся з Політикою конфіденційності + terms_of_service: Я погоджуюся з Умовами використання + agreements_must_accept: Ви повинні прийняти Умови використання та Політику + конфіденційності для продовження. + agreements_required: Ви повинні прийняти Політику конфіденційності та Умови + використання + captcha_validation_failed: Помилка перевірки безпеки. Будь ласка, пройдіть + перевірку безпеки та спробуйте знову. + code_of_conduct: + label: Я погоджуюся з Кодексом поведінки + email: + help: Будь ласка, введіть дійсну адресу електронної пошти. + label: Електронна пошта + invitation_code: Код запрошення + invitation_code_help_html: Платформа %{platform} вимагає + запрошення перед реєстрацією. Якщо вас було запрошено, будь ласка, введіть + ваш код запрошення в поле нижче для доступу до форми реєстрації. + invitation_required: Потрібне запрошення + password: + help: Використовуйте сильний пароль для безпеки вашого облікового запису. + label: Пароль + password_confirmation: + help: Будь ласка, підтвердіть ваш пароль. + label: Підтвердження пароля + person: + description: Опис + description_hint: Надайте короткий опис себе, щоб допомогти іншим краще + вас пізнати. + identifier: Ім'я користувача + identifier_hint_html: Ваш ідентифікатор - це унікальне ім'я користувача, + що ідентифікує ваш профіль на сайті %{platform}. + name: Ім'я + name_hint: Будь ласка, надайте ваше повне ім'я. + privacy_policy: + label: Я погоджуюся з Політикою конфіденційності + profile_details: Деталі профілю + sign_up: Зареєструватися + submit: Надіслати + terms_of_service: + label: Я погоджуюся з Умовами використання + view: Переглянути + signed_up: Ласкаво просимо! Ви успішно зареєструвалися. + signed_up_but_inactive: Ви успішно зареєструвалися. Проте ми не змогли авторизувати + вас, тому що ваш обліковий запис ще не активовано. + signed_up_but_locked: Ви успішно зареєструвалися. Проте ми не змогли авторизувати + вас, тому що ваш обліковий запис заблоковано. + signed_up_but_unconfirmed: Повідомлення з посиланням підтвердження було надіслано + на вашу адресу електронної пошти. Будь ласка, перейдіть за посиланням для + активації вашого облікового запису. + update_needs_confirmation: Ви успішно оновили свій обліковий запис, але нам + потрібно перевірити вашу нову адресу електронної пошти. Будь ласка, перевірте + свою електронну пошту та перейдіть за посиланням підтвердження, щоб підтвердити + вашу нову адресу електронної пошти. + updated: Ваш обліковий запис було успішно оновлено. + updated_but_not_signed_in: Ваш обліковий запис було успішно оновлено, але оскільки + ваш пароль було змінено, вам потрібно увійти знову. + sessions: + already_signed_out: Ви вже вийшли з системи. + new: + email: + help: Введіть вашу адресу електронної пошти для входу. + label: Електронна пошта + password: + help: Введіть ваш пароль для входу. + label: Пароль + toggle: Показати/Приховати пароль + remember_me: Запам'ятати мене + sign_in: Увійти + signed_in: Ви успішно увійшли до системи. + signed_out: Ви успішно вийшли з системи. + shared: + links: + back: Назад + didn_t_receive_confirmation_instructions: Не отримали інструкції підтвердження? + didn_t_receive_unlock_instructions: Не отримали інструкції розблокування? + forgot_your_password: Забули свій пароль? + sign_in: Увійти + sign_in_with_provider: Увійти через %{provider} + sign_up: Зареєструватися + minimum_password_length: "(мінімум %{count} символів)" + unlocks: + new: + resend_unlock_instructions: Повторно надіслати інструкції розблокування + send_instructions: Ви отримаєте електронний лист з інструкціями щодо розблокування + вашого облікового запису через кілька хвилин. + send_paranoid_instructions: Якщо ваш обліковий запис існує, ви отримаєте електронний + лист з інструкціями щодо його розблокування через кілька хвилин. + unlocked: Ваш обліковий запис було успішно розблоковано. Будь ласка, увійдіть + для продовження. + errors: + format: "%{attribute} %{message}" + internal_server_error: + description: Вибачте, але щось пішло не так з нашого боку. + title: 500 - Внутрішня помилка сервера + messages: + accepted: має бути прийнято + already_confirmed: вже підтверджено, будь ласка, спробуйте увійти + blank: не може бути пустим + confirmation: не співпадає з %{attribute} + confirmation_period_expired: потрібно підтвердити протягом %{period}, будь ласка + запросіть новий + empty: не може бути порожнім + equal_to: має дорівнювати %{count} + even: має бути парним + exclusion: зарезервовано + expired: закінчився, будь ласка, запросіть новий + greater_than: має бути більше %{count} + greater_than_or_equal_to: має бути більше або дорівнювати %{count} + in: має бути в %{count} + inclusion: не включено до списку + invalid: неправильний + less_than: має бути менше %{count} + less_than_or_equal_to: має бути менше або дорівнювати %{count} + model_invalid: 'Перевірка не вдалася: %{errors}' + not_a_number: не є числом + not_an_integer: має бути цілим числом + not_found: не знайдено + not_locked: не було заблоковано + not_saved: + few: "%{count} помилки заборонили збереження цього %{resource}:" + many: "%{count} помилок заборонили збереження цього %{resource}:" + one: '1 помилка заборонила збереження цього %{resource}:' + other: "%{count} помилок заборонили збереження цього %{resource}:" + odd: має бути непарним + other_than: має бути іншим ніж %{count} + present: має бути порожнім + required: має існувати + taken: вже зайнято + too_long: + few: занадто довгий (максимум %{count} символи) + many: занадто довгий (максимум %{count} символів) + one: занадто довгий (максимум %{count} символ) + other: занадто довгий (максимум %{count} символів) + too_short: + few: занадто короткий (мінімум %{count} символи) + many: занадто короткий (мінімум %{count} символів) + one: занадто короткий (мінімум %{count} символ) + other: занадто короткий (мінімум %{count} символів) + weak_password: недостатньо сильний. Розгляньте додавання цифри, символів або + більше літер, щоб зробити його сильнішим. Уникайте використання слова 'password', + свого дня народження, або інших звичайних легко вгадуваних паролів. + wrong_length: + few: неправильна довжина (має бути %{count} символи) + many: неправильна довжина (має бути %{count} символів) + one: неправильна довжина (має бути %{count} символ) + other: неправильна довжина (має бути %{count} символів) + models: + address_missing_type: Адреса має бути фізичною, поштовою або обома. + ends_at_before_starts_at: має бути після часу початку + host_single: можна встановити тільки для одного запису + person_checklist_item: + directional_incomplete: Напрямок неповний + protected_destroy: Цей запис захищено і не може бути видалено. + not_found: + description: Сторінка, яку ви шукаєте, можливо, була видалена, змінила назву + або тимчасово недоступна. + title: 404 - Сторінку не знайдено + person_block: + cannot_block_manager: не може бути менеджером платформи + cannot_block_self: не можете заблокувати себе + template: + body: 'Виникли проблеми з наступними полями:' + header: + few: "%{count} помилки заборонили збереження цієї %{model}" + many: "%{count} помилок заборонили збереження цієї %{model}" + one: "%{count} помилка заборонила збереження цієї %{model}" + other: "%{count} помилок заборонили збереження цієї %{model}" + wizard: + max_completions: Досягнуто максимальну кількість завершень для цього майстра + та визначення кроку. + one_uncompleted: Дозволено тільки один незавершений крок на людину. + step_limit: Кількість завершень для цього кроку досягла максимального ліміту + завершень майстра. + event: :activerecord.models.event + flash: + checklist_item: + update_failed: Не вдалося оновити елемент чек-листа. + updated: Елемент чек-листа оновлено. + generic: + created: "%{resource} було успішно створено." + deleted: Видалено + destroyed: "%{resource} було успішно знищено." + error_create: Помилка створення %{resource}. + error_remove: Не вдалося видалити %{resource}. + queued: "%{resource} поставлено в чергу для відправлення." + removed: "%{resource} було успішно видалено." + unauthorized: Неавторизовано + updated: "%{resource} було успішно оновлено." + joatu: + agreement: + accepted: Угоду прийнято + rejected: Угоду відхилено + response_links: + offer_created: Пропозицію створено у відповідь на запит. + request_created: Запит створено у відповідь на пропозицію. + person_block: + blocked: Особу було успішно заблоковано. + unblocked: Особу було успішно розблоковано. + globals: + accepted: Прийнято + actions: Дії + add_block: Додати блок + add_child_item: Додати дочірній елемент + add_member: Додати учасника + ago: тому + all: Все + are_you_sure: Ви впевнені + back: Назад + back_to_list: Назад до списку + cancel: Скасувати + clear: Очистити + clear_filter: Очистити фільтр + confirm_delete: Ви впевнені, що хочете видалити цей запис? + create: Створити + declined: Відхилено + delete: Видалити + destroy: Знищити + draft: Чернетка + edit: Редагувати + email: Електронна пошта + 'false': Ні + forms: + save: Зберегти + from: Від + hidden: Приховано + loading: Завантаження... + locale: Локаль + message: Повідомлення + new: Новий + 'no': Ні + no_description: Немає опису + no_image: Немає зображення + no_invitee: Немає запрошеного + none: Немає + not_sent: Не відправлено + pagination: + of: з + showing: Показано + to: до + pending: Очікує + platform_not_public: Платформа наразі приватна. Будь ласка, увійдіть для доступу. + published: Опубліковано + remove: Видалити + resend: Повторно надіслати + save: Зберегти + sent: Відправлено + show: Показати + status: Статус + tabs: + about: Про + attendees: Учасники + contact: Контакт + events: Події + invitations: Запрошення + members: Учасники + to: До + 'true': так + undo_clear: Скасувати очищення + update: Оновити + url: URL + view: Переглянути + visible: Видимий + 'yes': Так + helpers: + close: Закрити + errors: + heading: Помилки форми + prohibited: Заборонено + hint: + call_for_interest: + cover_image: Зображення обкладинки + community: + cover_image: Зображення обкладинки + logo: Логотип + profile_image: Зображення профілю + event: + cover_image: Зображення обкладинки + images: + cover_image: Зображення обкладинки + person: + allow_messages_from_members: Дозволити отримувати повідомлення від людей, + окрім менеджерів платформи. + cover_image: Завантажте зображення обкладинки для відображення у верхній частині + профілю. + description: Надайте короткий опис або біографію. + locale: Виберіть бажану мову для особи. + name: Введіть повне ім'я особи. + notify_by_email: Надсилати електронний лист, коли в розмові є непрочитане + повідомлення. + profile_image: Завантажте зображення профілю для особи. + show_conversation_details: Показувати назву розмови та ім'я відправника в + сповіщеннях. + slug: URL-дружній ідентифікатор, зазвичай генерується автоматично. + required_info: Це поле обов'язкове + label: + person: + cover_image: Зображення обкладинки + description: Опис + name: Ім'я + profile_image: Зображення профілю + slug: Слаг + language_select: + prompt: Підказка + required_info: Це поле обов'язкове + select: + prompt: Будь ласка, виберіть + submit: + create: Створити %{model} + submit: Зберегти %{model} + update: Оновити %{model} + toolbar: + aria_label: Панель інструментів редактора + hints: + authors: + select_multiple: Оберіть одного або кількох авторів + categories: + select_multiple: Оберіть кілька категорій + datetime_field: Оберіть дату та час для цього поля. + language_select: Оберіть мову для цього поля. + privacy_field: Встановіть рівень конфіденційності для цього поля. + required_field: Це поле є обов'язковим. + resource: + type: Тип + type_select_field: Оберіть тип для цього поля. + host_dashboard: + index: + better_together: Разом краще + content: Контент + geography: Географія + page_title: Панель керування хоста + title: Панель керування хоста + resource_card: + new_resource: Новий %{resource} + none_yet: Поки що немає + total_resources: Всього ресурсів + view_all: Переглянути всі + invitations: + calculating: Обчислення + locales: + en: English + es: Español + fr: Français + uk: Українська + meta: + default_description: Ласкаво просимо на %{platform_name} + page: + description_fallback: Читайте %{title} на %{platform_name} + metrics: + search_queries: + invalid_parameters: Недійсні параметри для відстеження пошуку. + shares: + invalid_parameters: Недійсні параметри + navbar: + accept_invitation: Accept invitation + conversations: Conversations + host_mgmt_tooltip: Host Management + locale_switcher_tooltip: Change Language + log_out: Log Out + my_profile: Мій профіль + notifications_tooltip: Сповіщення + search_tooltip: Пошук + settings: Налаштування + sign_in: Увійти + toggle_navigation: Переключити навігацію + user_nav_tooltip: Меню облікового запису + navigation: + header: + events: Події + exchange_hub: Центр обміну + navigation_item: + update_failed: Оновлення не вдалося + updated: Оновлено + 'no': Ні + number: + currency: + format: + delimiter: "," + format: "%u%n" + precision: 2 + separator: "." + significant: false + strip_insignificant_zeros: false + unit: "$" + format: + delimiter: "," + precision: 3 + round_mode: default + separator: "." + significant: false + strip_insignificant_zeros: false + human: + decimal_units: + format: "%n %u" + units: + billion: Мільярд + million: Мільйон + quadrillion: Квадрильйон + thousand: Тисяча + trillion: Трильйон + unit: '' + format: + delimiter: '' + precision: 3 + significant: true + strip_insignificant_zeros: true + storage_units: + format: "%n %u" + units: + byte: + few: Байти + many: Байт + one: Байт + other: Байт + eb: ЕБ + gb: ГБ + kb: КБ + mb: МБ + pb: ПБ + tb: ТБ + percentage: + format: + delimiter: '' + format: "%n%" + precision: + format: + delimiter: '' + og: + default_description: Ласкаво просимо на %{platform_name} + default_title: "%{platform_name}" + page: + description_fallback: Читайте %{title} на %{platform_name} + title: "%{title} | %{platform_name}" + pages: + confirm_destroy: Підтвердіть видалення + index: + clear_filter: Очистити фільтр + clear_filters: Очистити фільтри + filter: Фільтр + search_by_title: Пошук за назвою... + partners: + confirm_delete: Підтвердіть видалення + people: + confirm_delete: Підтвердіть видалення + platform_invitations: + confirm_delete: Підтвердіть видалення + platforms: + confirm_delete: Підтвердіть видалення + pundit: + errors: + create: Вам не дозволено створювати цей %{resource}. + default: Вам не дозволено виконувати цю дію з цим %{resource}. + destroy: Вам не дозволено видаляти цей %{resource}. + edit: Вам не дозволено редагувати цей %{resource}. + index: Вам не дозволено переглядати список %{resource}. + leave_conversation: Ви є останнім учасником розмови і не можете її покинути + new: Вам не дозволено створювати новий %{resource}. + show: Вам не дозволено переглядати цей %{resource}. + update: Вам не дозволено оновлювати цей %{resource}. + resource_permissions: + confirm_destroy: Підтвердіть видалення + resources: + block: Блок + calendar: Календар + checklist: Чек-лист + community: Спільнота + continent: Континент + country: Країна + download_failed: Завантаження не вдалося + invitation: Запрошення + invitation_email: Електронна пошта запрошення + member: Учасник + navigation_area: Область навігації + page: Сторінка + person: Особа + person_platform_membership: Членство особи на платформі + platform: Платформа + profile: Профіль + region: Регіон + region_settlement: Регіональне поселення + report: Звіт + resource_permission: Дозвіл ресурсу + role: Роль + settlement: Поселення + state: Штат/Область + user: Користувач + roles: + confirm_destroy: Підтвердіть видалення + search: + button: Пошук + placeholder: Пошук... + shared: + by: автор + links: + back: Назад + simple_calendar: + next: Наступний + previous: Попередній + today: Сьогодні + week: Тиждень + support: + array: + last_word_connector: " та " + two_words_connector: " та " + words_connector: ", " + time: + am: дп + formats: + dashboard_resource: "%d %b, %Y %H:%M" + date_picker: Вибір дати + datetime_picker: Вибір дати і часу + default: "%d %B %Y р. %-H:%M" + event: "%d %B %Y р. о %-H:%M" + event_date_time: "%d %b, %-H:%M" + event_date_time_with_year: "%d %b, %Y %-H:%M" + long: "%d %B %Y р. %-H:%M" + short: "%d %b %-H:%M" + time_only: "%-H:%M" + time_only_with_year: "%-H:%M %Y" + pm: пп + views: + buttons: + back: Назад + create_report: Створити звіт + download: Завантажити + go_home: На головну + new: Новий %{resource} + view_page: Переглянути сторінку + headers: + link_checker_reports: Звіти перевірки посилань + link_click_reports: Звіти кліків по посиланням + new_link_checker_report: Новий звіт перевірки посилань + new_link_click_report: Новий звіт кліків по посиланням + new_page_view_report: Новий звіт переглядів сторінок + page_view_reports: Звіти переглядів сторінок + labels: + actions: Дії + all: Всі + created_at: Створено + external: Зовнішній + file_format: Формат файлу + filters: Фільтри + from_date: З дати + id: ID + internal: Внутрішній + internal_filter: Внутрішній фільтр + no_file: Немає файлу + pageable_type_filter: Фільтр типу сторінки + please_correct: 'Будь ласка, виправте наступні помилки:' + protected: Захищений + sort_by_total_clicks: Сортувати за загальною кількістю кліків + sort_by_total_views: Сортувати за загальною кількістю переглядів + to_date: До дати + pagination: + aria_label: Навігація пагінації + current: "(поточна)" + first: Перша + last: Остання + next: Наступна + previous: Попередня + showing_entries: Показано %{from} до %{to} із %{total} записів + truncate: "…" + 'yes': Так diff --git a/lib/better_together/engine.rb b/lib/better_together/engine.rb index 0344ade23..617b45d5e 100644 --- a/lib/better_together/engine.rb +++ b/lib/better_together/engine.rb @@ -14,6 +14,7 @@ require 'devise' require 'devise-i18n' require 'devise/jwt' +require 'devise_zxcvbn' require 'elasticsearch/model' require 'elasticsearch/rails' require 'font-awesome-sass' diff --git a/spec/controllers/better_together/person_blocks_controller_spec.rb b/spec/controllers/better_together/person_blocks_controller_spec.rb index e72443ed4..3b07a885b 100644 --- a/spec/controllers/better_together/person_blocks_controller_spec.rb +++ b/spec/controllers/better_together/person_blocks_controller_spec.rb @@ -9,7 +9,7 @@ routes { BetterTogether::Engine.routes } let(:locale) { I18n.default_locale } - let(:user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let(:user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let(:person) { user.person } let(:blocked_person) { create(:better_together_person) } let(:another_person) { create(:better_together_person) } diff --git a/spec/controllers/better_together/platform_invitations_controller_spec.rb b/spec/controllers/better_together/platform_invitations_controller_spec.rb index f3bd509da..ec0f36749 100644 --- a/spec/controllers/better_together/platform_invitations_controller_spec.rb +++ b/spec/controllers/better_together/platform_invitations_controller_spec.rb @@ -9,7 +9,7 @@ routes { BetterTogether::Engine.routes } let(:locale) { I18n.default_locale } - let(:user) { find_or_create_test_user('platform_manager@example.test', 'password12345', :platform_manager) } + let(:user) { find_or_create_test_user('platform_manager@example.test', 'SecureTest123!@#', :platform_manager) } let(:person) { user.person } let(:platform) { create(:better_together_platform, name: 'Test Platform') } let(:platform_slug) { platform.slug } diff --git a/spec/controllers/better_together/users/registrations_controller_hook_spec.rb b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb index e62d8039d..e5d4237aa 100644 --- a/spec/controllers/better_together/users/registrations_controller_hook_spec.rb +++ b/spec/controllers/better_together/users/registrations_controller_hook_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe BetterTogether::Users::RegistrationsController, :skip_host_setup do +RSpec.describe BetterTogether::Users::RegistrationsController, :skip_host_setup, :user_registration do include BetterTogether::CapybaraFeatureHelpers routes { BetterTogether::Engine.routes } diff --git a/spec/features/agreements/registration_consent_spec.rb b/spec/features/agreements/registration_consent_spec.rb index ea6efe3dc..3d2f415c4 100644 --- a/spec/features/agreements/registration_consent_spec.rb +++ b/spec/features/agreements/registration_consent_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe 'User registration agreements', :as_platform_manager do +RSpec.describe 'User registration agreements', :as_platform_manager, :user_registration do let!(:privacy_agreement) { BetterTogether::Agreement.find_by!(identifier: 'privacy_policy') } # rubocop:enable RSpec/LetSetup # rubocop:todo RSpec/LetSetup @@ -13,8 +13,8 @@ visit new_user_registration_path(locale: I18n.default_locale) fill_in 'user[email]', with: 'test@example.test' - fill_in 'user[password]', with: 'password12345' - fill_in 'user[password_confirmation]', with: 'password12345' + fill_in 'user[password]', with: 'SecureTest123!@#' + fill_in 'user[password_confirmation]', with: 'SecureTest123!@#' fill_in 'user[person_attributes][name]', with: 'Test User' fill_in 'user[person_attributes][identifier]', with: 'testuser' fill_in 'user[person_attributes][description]', with: 'Tester' @@ -30,8 +30,8 @@ visit new_user_registration_path(locale: I18n.default_locale) fill_in 'user[email]', with: 'test@example.test' - fill_in 'user[password]', with: 'password12345' - fill_in 'user[password_confirmation]', with: 'password12345' + fill_in 'user[password]', with: 'SecureTest123!@#' + fill_in 'user[password_confirmation]', with: 'SecureTest123!@#' fill_in 'user[person_attributes][name]', with: 'Test User' fill_in 'user[person_attributes][identifier]', with: 'testuser' fill_in 'user[person_attributes][description]', with: 'Tester' diff --git a/spec/features/checklist_create_appends_spec.rb b/spec/features/checklist_create_appends_spec.rb index faa30845b..b231fe927 100644 --- a/spec/features/checklist_create_appends_spec.rb +++ b/spec/features/checklist_create_appends_spec.rb @@ -5,7 +5,7 @@ RSpec.describe 'Checklist item creation appends to bottom', :js do include ActionView::RecordIdentifier - let(:manager) { find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) } + let(:manager) { find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) } before do ensure_essential_data! diff --git a/spec/features/checklist_person_completion_spec.rb b/spec/features/checklist_person_completion_spec.rb index 5da732dc6..a4c62f388 100644 --- a/spec/features/checklist_person_completion_spec.rb +++ b/spec/features/checklist_person_completion_spec.rb @@ -9,7 +9,7 @@ let!(:person) { create(:better_together_person, user: user) } before do - find_or_create_test_user('user@example.test', 'password12345', :user) + find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) capybara_login_as_user end diff --git a/spec/features/checklist_reorder_system_spec.rb b/spec/features/checklist_reorder_system_spec.rb index 2b24c2aa0..5fccbf678 100644 --- a/spec/features/checklist_reorder_system_spec.rb +++ b/spec/features/checklist_reorder_system_spec.rb @@ -6,7 +6,7 @@ include ActionView::RecordIdentifier # Use the standard test manager credentials (password must meet length requirements) - let(:manager) { find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) } + let(:manager) { find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) } before do # Ensure essential data exists and log in diff --git a/spec/features/devise/registration_spec.rb b/spec/features/devise/registration_spec.rb index 2c989c407..bd86a6ccd 100644 --- a/spec/features/devise/registration_spec.rb +++ b/spec/features/devise/registration_spec.rb @@ -3,12 +3,12 @@ require 'rails_helper' # rubocop:disable Metrics/BlockLength -RSpec.feature 'User Registration' do +RSpec.feature 'User Registration', :user_registration do # Ensure you have a valid user created; using FactoryBot here let!(:host_setup_wizard) do BetterTogether::Wizard.find_or_create_by(identifier: 'host_setup') end - let!(:user) { build(:better_together_user) } + let!(:user) { build(:better_together_user, password: 'SecureTest123!@#', password_confirmation: 'SecureTest123!@#') } let!(:person) { build(:better_together_person) } let!(:privacy_agreement) do BetterTogether::Agreement.find_or_create_by(identifier: 'privacy_policy') do |a| @@ -40,9 +40,16 @@ scenario 'User registers successfully' do # rubocop:todo RSpec/MultipleExpectations # rubocop:enable RSpec/MultipleExpectations host_setup_wizard.mark_completed + # Debug: check that we have the right setup + puts "Current path before visit: #{current_path}" + puts "Host platform exists: #{BetterTogether::Platform.find_by(host: true).present?}" + puts "Wizard completed: #{host_setup_wizard.completed?}" # byebug # Visit the sign-in page (adjust the path if your routes differ) visit better_together.new_user_registration_path + puts "Current path after visit: #{current_path}" + puts "Page title: #{page.title}" + puts "Page has Sign Up: #{page.has_content?('Sign Up')}" # Fill in the Devise login form fill_in 'user[email]', with: user.email @@ -52,26 +59,66 @@ fill_in 'user[person_attributes][name]', with: person.name fill_in 'user[person_attributes][identifier]', with: person.identifier + puts 'Looking for agreement checkboxes:' + puts " terms_of_service_agreement: #{page.has_field?('terms_of_service_agreement')}" + puts " privacy_policy_agreement: #{page.has_field?('privacy_policy_agreement')}" + puts " code_of_conduct_agreement: #{page.has_field?('code_of_conduct_agreement')}" + if page.has_unchecked_field?('terms_of_service_agreement') + puts ' Checking terms_of_service_agreement' check 'terms_of_service_agreement' elsif page.has_unchecked_field?('user_accept_terms_of_service') + puts ' Checking user_accept_terms_of_service' check 'user_accept_terms_of_service' end if page.has_unchecked_field?('privacy_policy_agreement') + puts ' Checking privacy_policy_agreement' check 'privacy_policy_agreement' elsif page.has_unchecked_field?('user_accept_privacy_policy') + puts ' Checking user_accept_privacy_policy' check 'user_accept_privacy_policy' end if page.has_unchecked_field?('code_of_conduct_agreement') + puts ' Checking code_of_conduct_agreement' check 'code_of_conduct_agreement' elsif page.has_unchecked_field?('user_accept_code_of_conduct') + puts ' Checking user_accept_code_of_conduct' check 'user_accept_code_of_conduct' end # Click the login button (make sure the button text matches your view) + puts "Before clicking Sign Up - User count: #{BetterTogether::User.count}" + puts "Before clicking Sign Up - Page has button: #{page.has_button?('Sign Up')}" click_button 'Sign Up' + puts "After clicking Sign Up - Current path: #{current_path}" + puts "After clicking Sign Up - User count: #{BetterTogether::User.count}" + puts "After clicking Sign Up - Person count: #{BetterTogether::Person.count}" + puts "After clicking Sign Up - Identification count: #{BetterTogether::Identification.count}" + created_user = BetterTogether::User.last + puts "Created user ID: #{created_user&.id}" + puts "Created user has person: #{created_user&.person.present?}" + puts "Person ID: #{created_user&.person&.id}" if created_user&.person + puts "Person persisted: #{created_user&.person&.persisted?}" if created_user&.person + puts "Person errors: #{created_user.person.errors.full_messages}" if created_user&.person&.errors&.any? + puts "Person identification exists: #{created_user&.person_identification.present?}" if created_user + if created_user&.person_identification + puts "Person identification persisted: #{created_user&.person_identification&.persisted?}" + end + + # Check if person was created separately + all_persons = BetterTogether::Person.all + puts "All persons: #{all_persons.map(&:id)}" + + # Check if there are any identifications + all_identifications = BetterTogether::Identification.all + puts "All identifications: #{all_identifications.map do |i| + "#{i.id} -> agent: #{i.agent_type}##{i.agent_id}, identity: #{i.identity_type}##{i.identity_id}" + end}" + puts "After clicking Sign Up - AgreementParticipant count: #{BetterTogether::AgreementParticipant.count}" + puts "After clicking Sign Up - PersonCommunityMembership count: #{BetterTogether::PersonCommunityMembership.count}" + puts "Page content after submit: #{page.body.include?('error') ? 'CONTAINS ERRORS' : 'NO ERRORS VISIBLE'}" # Expect a confirmation message (this text may vary based on your flash messages) # rubocop:disable Layout/LineLength diff --git a/spec/features/invitations/platform_invitation_accept_spec.rb b/spec/features/invitations/platform_invitation_accept_spec.rb index 27fb05b31..c0d6d0102 100644 --- a/spec/features/invitations/platform_invitation_accept_spec.rb +++ b/spec/features/invitations/platform_invitation_accept_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe 'accepting a platform invitation' do +RSpec.describe 'accepting a platform invitation', :user_registration do let!(:invitation) do create(:better_together_platform_invitation, invitable: configure_host_platform) diff --git a/spec/features/notifications/mark_as_read_spec.rb b/spec/features/notifications/mark_as_read_spec.rb index 3847fcc9e..3d5f067f5 100644 --- a/spec/features/notifications/mark_as_read_spec.rb +++ b/spec/features/notifications/mark_as_read_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe 'message notifications', :as_user do - let(:user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let(:user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let(:person) { user.person } let(:conversation) { create(:conversation, creator: person) } # let!(:conversation_participant) do diff --git a/spec/mailers/better_together/event_mailer_spec.rb b/spec/mailers/better_together/event_mailer_spec.rb index bd5b41134..697ad4bfd 100644 --- a/spec/mailers/better_together/event_mailer_spec.rb +++ b/spec/mailers/better_together/event_mailer_spec.rb @@ -41,7 +41,9 @@ module BetterTogether # rubocop:todo Metrics/ModuleLength context 'when event has location' do it 'includes location information' do - expect(mail.body.encoded).to include(event.location_display_name) + # HTML-encoded version of location name due to ERB escaping + expected_location = ERB::Util.html_escape(event.location_display_name) + expect(mail.body.encoded).to include(expected_location) end end diff --git a/spec/policies/better_together/conversation_policy_spec.rb b/spec/policies/better_together/conversation_policy_spec.rb index 09aa40c19..cedcbb50e 100644 --- a/spec/policies/better_together/conversation_policy_spec.rb +++ b/spec/policies/better_together/conversation_policy_spec.rb @@ -7,7 +7,7 @@ let!(:host_platform) { configure_host_platform } - let!(:manager_user) { create(:user, :confirmed, :platform_manager, password: 'password12345') } + let!(:manager_user) { create(:user, :confirmed, :platform_manager, password: 'SecureTest123!@#') } let!(:manager_person) { manager_user.person } let!(:opted_in_person) do @@ -26,7 +26,7 @@ end context 'when agent is a regular member' do - let!(:regular_user) { create(:user, :confirmed, password: 'password12345') } + let!(:regular_user) { create(:user, :confirmed, password: 'SecureTest123!@#') } it 'includes platform managers and opted-in members, but not non-opted members' do # rubocop:enable RSpec/MultipleExpectations @@ -39,7 +39,7 @@ end describe '#create? with participants kwarg' do - let(:regular_user) { create(:user, :confirmed, password: 'password12345') } + let(:regular_user) { create(:user, :confirmed, password: 'SecureTest123!@#') } let(:policy) { described_class.new(regular_user, BetterTogether::Conversation.new) } it 'allows create when user present and participants are permitted' do diff --git a/spec/requests/better_together/checklist_items_reorder_spec.rb b/spec/requests/better_together/checklist_items_reorder_spec.rb index 32c70971a..aee5d0649 100644 --- a/spec/requests/better_together/checklist_items_reorder_spec.rb +++ b/spec/requests/better_together/checklist_items_reorder_spec.rb @@ -3,11 +3,14 @@ require 'rails_helper' RSpec.describe 'ChecklistItems Reorder' do + include AutomaticTestConfiguration + let(:locale) { I18n.default_locale } - let(:platform_manager) { find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) } + let(:platform_manager) { find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) } before do - login(platform_manager.email, 'password12345') + configure_host_platform + login(platform_manager.email, 'SecureTest123!@#') end # rubocop:todo RSpec/MultipleExpectations diff --git a/spec/requests/better_together/checklists_spec.rb b/spec/requests/better_together/checklists_spec.rb index 81cff553e..32d2a64f4 100644 --- a/spec/requests/better_together/checklists_spec.rb +++ b/spec/requests/better_together/checklists_spec.rb @@ -59,11 +59,11 @@ # rubocop:todo RSpec/MultipleExpectations it 'allows creator to update their checklist' do # rubocop:todo RSpec/MultipleExpectations # rubocop:enable RSpec/MultipleExpectations - user = create(:better_together_user, :confirmed, password: 'password12345') + user = create(:better_together_user, :confirmed, password: 'SecureTest123!@#') checklist = create(:better_together_checklist, creator: user.person) # sign in as that user - login(user.email, 'password12345') + login(user.email, 'SecureTest123!@#') patch better_together.checklist_path(checklist, locale: I18n.default_locale), params: { checklist: { title_en: 'Creator Update' } } diff --git a/spec/requests/better_together/conversation_message_protection_spec.rb b/spec/requests/better_together/conversation_message_protection_spec.rb index 7a21ae7fb..e232308a0 100644 --- a/spec/requests/better_together/conversation_message_protection_spec.rb +++ b/spec/requests/better_together/conversation_message_protection_spec.rb @@ -11,11 +11,12 @@ configure_host_platform # Setup: create a manager user (owner of the conversation) and another user - manager_user = create(:user, :confirmed, :platform_manager, email: 'owner@example.test', password: 'password12345') - other_user = create(:user, :confirmed, email: 'attacker@example.test', password: 'password12345') + manager_user = create(:user, :confirmed, :platform_manager, email: 'owner@example.test', + password: 'SecureTest123!@#') + other_user = create(:user, :confirmed, email: 'attacker@example.test', password: 'SecureTest123!@#') # Create a conversation as the manager with a nested message - login(manager_user.email, 'password12345') + login(manager_user.email, 'SecureTest123!@#') post better_together.conversations_path(locale: I18n.default_locale), params: { conversation: { @@ -34,7 +35,7 @@ # Now sign in as other_user and attempt to change manager's message via PATCH logout - login(other_user.email, 'password12345') + login(other_user.email, 'SecureTest123!@#') patch better_together.conversation_path(conversation, locale: I18n.default_locale), params: { conversation: { diff --git a/spec/requests/better_together/conversations_create_with_message_spec.rb b/spec/requests/better_together/conversations_create_with_message_spec.rb index dd3a7bb54..bed471c3a 100644 --- a/spec/requests/better_together/conversations_create_with_message_spec.rb +++ b/spec/requests/better_together/conversations_create_with_message_spec.rb @@ -10,9 +10,9 @@ # Ensure the test user exists and is confirmed unless BetterTogether::User.find_by(email: 'user@example.test') create(:user, :confirmed, email: 'user@example.test', - password: 'password12345') + password: 'SecureTest123!@#') end - login('user@example.test', 'password12345') + login('user@example.test', 'SecureTest123!@#') end # rubocop:todo RSpec/MultipleExpectations diff --git a/spec/requests/better_together/conversations_request_spec.rb b/spec/requests/better_together/conversations_request_spec.rb index dd44dcbf0..76526e36c 100644 --- a/spec/requests/better_together/conversations_request_spec.rb +++ b/spec/requests/better_together/conversations_request_spec.rb @@ -6,7 +6,7 @@ include RequestSpecHelper let!(:manager_user) do - create(:user, :confirmed, :platform_manager, email: 'manager1@example.test', password: 'password12345') + create(:user, :confirmed, :platform_manager, email: 'manager1@example.test', password: 'SecureTest123!@#') end let!(:opted_in_person) do create(:better_together_person, preferences: { receive_messages_from_members: true }, name: 'Opted In User') diff --git a/spec/requests/better_together/event_invitation_token_processing_spec.rb b/spec/requests/better_together/event_invitation_token_processing_spec.rb index 5ec1e3796..0ea5851fa 100644 --- a/spec/requests/better_together/event_invitation_token_processing_spec.rb +++ b/spec/requests/better_together/event_invitation_token_processing_spec.rb @@ -11,7 +11,7 @@ existing end end - let!(:manager_user) { find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) } + let!(:manager_user) { find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) } let!(:event) do BetterTogether::Event.create!( diff --git a/spec/requests/better_together/event_invitations_spec.rb b/spec/requests/better_together/event_invitations_spec.rb index d127884ad..897177608 100644 --- a/spec/requests/better_together/event_invitations_spec.rb +++ b/spec/requests/better_together/event_invitations_spec.rb @@ -122,11 +122,11 @@ # Ensure user exists and logged in as regular user user = BetterTogether::User.find_by(email: 'user@example.test') || - create(:better_together_user, :confirmed, email: 'user@example.test', password: 'password12345') + create(:better_together_user, :confirmed, email: 'user@example.test', password: 'SecureTest123!@#') # Clear any existing session and login as the specific user logout if respond_to?(:logout) - login(user.email, 'password12345') + login(user.email, 'SecureTest123!@#') post better_together.accept_invitation_path(invitation.token, locale: locale) @@ -148,11 +148,11 @@ valid_from: Time.current) user = BetterTogether::User.find_by(email: 'user@example.test') || - create(:better_together_user, :confirmed, email: 'user@example.test', password: 'password12345') + create(:better_together_user, :confirmed, email: 'user@example.test', password: 'SecureTest123!@#') # Clear any existing session and login as the specific user logout if respond_to?(:logout) - login(user.email, 'password12345') + login(user.email, 'SecureTest123!@#') post better_together.decline_invitation_path(invitation.token, locale: locale) diff --git a/spec/requests/better_together/events_controller_spec.rb b/spec/requests/better_together/events_controller_spec.rb index 202f45ba1..8c4844ab0 100644 --- a/spec/requests/better_together/events_controller_spec.rb +++ b/spec/requests/better_together/events_controller_spec.rb @@ -87,7 +87,7 @@ describe 'RSVP actions' do let(:user_email) { 'manager@example.test' } - let(:password) { 'password12345' } + let(:password) { 'SecureTest123!@#' } let(:event) do BetterTogether::Event.create!(name: 'RSVP Test', starts_at: 1.day.from_now, identifier: SecureRandom.uuid) end diff --git a/spec/requests/better_together/joatu/agreements_spec.rb b/spec/requests/better_together/joatu/agreements_spec.rb index b97987f2f..a870fdbe4 100644 --- a/spec/requests/better_together/joatu/agreements_spec.rb +++ b/spec/requests/better_together/joatu/agreements_spec.rb @@ -4,7 +4,7 @@ # rubocop:disable Metrics/BlockLength RSpec.describe 'BetterTogether::Joatu::Agreements', :as_user do - let(:user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let(:user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let(:person) { user.person } let(:offer) { create(:joatu_offer) } let(:request_record) { create(:joatu_request, creator: person) } diff --git a/spec/requests/better_together/joatu/offers_aggregated_matches_spec.rb b/spec/requests/better_together/joatu/offers_aggregated_matches_spec.rb index 765c82b5f..57de55d68 100644 --- a/spec/requests/better_together/joatu/offers_aggregated_matches_spec.rb +++ b/spec/requests/better_together/joatu/offers_aggregated_matches_spec.rb @@ -9,7 +9,7 @@ # Current authenticated user creating an offer current_user = BetterTogether::User.find_by(email: 'user@example.test') || FactoryBot.create(:better_together_user, :confirmed, - email: 'user@example.test', password: 'password12345') + email: 'user@example.test', password: 'SecureTest123!@#') my_person = current_user.person # Ensure both records share a category so they match diff --git a/spec/requests/better_together/joatu/offers_spec.rb b/spec/requests/better_together/joatu/offers_spec.rb index 4fb5c3864..885fec40f 100644 --- a/spec/requests/better_together/joatu/offers_spec.rb +++ b/spec/requests/better_together/joatu/offers_spec.rb @@ -4,7 +4,7 @@ # rubocop:disable Metrics/BlockLength RSpec.describe 'BetterTogether::Joatu::Offers', :as_user do - let(:user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let(:user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let(:person) { user.person } let(:category) { create(:better_together_joatu_category) } let(:valid_attributes) do diff --git a/spec/requests/better_together/joatu/requests_aggregated_matches_spec.rb b/spec/requests/better_together/joatu/requests_aggregated_matches_spec.rb index 45bfa4f0e..f53afbae4 100644 --- a/spec/requests/better_together/joatu/requests_aggregated_matches_spec.rb +++ b/spec/requests/better_together/joatu/requests_aggregated_matches_spec.rb @@ -9,7 +9,7 @@ # Current authenticated user creating a request current_user = BetterTogether::User.find_by(email: 'user@example.test') || FactoryBot.create(:better_together_user, :confirmed, - email: 'user@example.test', password: 'password12345') + email: 'user@example.test', password: 'SecureTest123!@#') my_person = current_user.person # Ensure both records share a category so they match diff --git a/spec/requests/better_together/joatu/requests_spec.rb b/spec/requests/better_together/joatu/requests_spec.rb index 7331d3536..9ab4227c7 100644 --- a/spec/requests/better_together/joatu/requests_spec.rb +++ b/spec/requests/better_together/joatu/requests_spec.rb @@ -7,7 +7,7 @@ include AutomaticTestConfiguration let(:locale) { I18n.default_locale } - let(:person) { find_or_create_test_user('user@example.test', 'password12345', :user).person } + let(:person) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user).person } let(:category) { create(:better_together_joatu_category) } let(:valid_attributes) do { name: 'New Request', description: 'Request description', creator_id: person.id, diff --git a/spec/requests/better_together/joatu/response_links_controller_spec.rb b/spec/requests/better_together/joatu/response_links_controller_spec.rb index 99dadc127..ce3a7a892 100644 --- a/spec/requests/better_together/joatu/response_links_controller_spec.rb +++ b/spec/requests/better_together/joatu/response_links_controller_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' RSpec.describe BetterTogether::Joatu::ResponseLinksController, :as_user do - let(:user) { create(:user, :confirmed, password: 'password12345') } + let(:user) { create(:user, :confirmed, password: 'SecureTest123!@#') } let(:person) { user.person } let(:offer) { create(:better_together_joatu_offer, creator: person) } let(:request_resource) { create(:better_together_joatu_request) } diff --git a/spec/requests/better_together/notifications_controller_spec.rb b/spec/requests/better_together/notifications_controller_spec.rb index 6c07dc624..8f87a39db 100644 --- a/spec/requests/better_together/notifications_controller_spec.rb +++ b/spec/requests/better_together/notifications_controller_spec.rb @@ -5,7 +5,7 @@ module BetterTogether # rubocop:todo Metrics/ModuleLength RSpec.describe NotificationsController, :as_user do let!(:notification) { create(:noticed_notification, recipient: person) } - let!(:user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let!(:user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let(:person) { user.person } describe 'GET #index' do diff --git a/spec/requests/better_together/person_checklist_items_spec.rb b/spec/requests/better_together/person_checklist_items_spec.rb index 733d1110c..6fa54af03 100644 --- a/spec/requests/better_together/person_checklist_items_spec.rb +++ b/spec/requests/better_together/person_checklist_items_spec.rb @@ -13,8 +13,8 @@ before do configure_host_platform # Use project's HTTP login helper to satisfy route constraints - test_user = find_or_create_test_user(user.email, 'password12345', :user) - login(test_user.email, 'password12345') + test_user = find_or_create_test_user(user.email, 'SecureTest123!@#', :user) + login(test_user.email, 'SecureTest123!@#') end # rubocop:todo RSpec/MultipleExpectations diff --git a/spec/requests/better_together/platform_privacy_with_event_invitations_spec.rb b/spec/requests/better_together/platform_privacy_with_event_invitations_spec.rb index de42a5951..6c3fc6d48 100644 --- a/spec/requests/better_together/platform_privacy_with_event_invitations_spec.rb +++ b/spec/requests/better_together/platform_privacy_with_event_invitations_spec.rb @@ -7,8 +7,8 @@ let(:locale) { I18n.default_locale } let!(:platform) { configure_host_platform } - let!(:manager_user) { find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) } - let!(:regular_user) { find_or_create_test_user('user@example.test', 'password12345', :user) } + let!(:manager_user) { find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) } + let!(:regular_user) { find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) } let!(:private_event) do create(:better_together_event, @@ -161,8 +161,8 @@ post user_registration_path(locale: locale), params: { user: { email: invitation.invitee_email, - password: 'password12345', - password_confirmation: 'password12345', + password: 'SecureTest123!@#', + password_confirmation: 'SecureTest123!@#', person_attributes: { name: 'New User', identifier: 'newuser' @@ -178,7 +178,7 @@ created_user = BetterTogether::User.find_by(email: invitation.invitee_email) created_user.confirm - login(invitation.invitee_email, 'password12345') + login(invitation.invitee_email, 'SecureTest123!@#') # Should redirect to the event after successful registration. Compare by slug to avoid locale path differences expect(response.request.fullpath).to include(event.slug) diff --git a/spec/requests/better_together/users/registrations_spec.rb b/spec/requests/better_together/users/registrations_spec.rb new file mode 100644 index 000000000..b8c35807f --- /dev/null +++ b/spec/requests/better_together/users/registrations_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'User Registration', :skip_host_setup do + include AutomaticTestConfiguration + + before do + configure_host_platform + end + + describe 'POST /en/users' do + let(:valid_user_params) do + { + email: 'test@example.com', + password: 'SecureTest123!@#', + password_confirmation: 'SecureTest123!@#', + person_attributes: { + name: 'Test User', + identifier: 'test-user' + } + } + end + + let!(:privacy_agreement) do + BetterTogether::Agreement.find_or_create_by(identifier: 'privacy_policy') do |a| + a.title = 'Privacy Policy' + a.creator = create(:person) + end + end + + let!(:terms_agreement) do + BetterTogether::Agreement.find_or_create_by(identifier: 'terms_of_service') do |a| + a.title = 'Terms of Service' + a.creator = create(:person) + end + end + + let!(:code_of_conduct_agreement) do + BetterTogether::Agreement.find_or_create_by(identifier: 'code_of_conduct') do |a| + a.title = 'Code of Conduct' + a.creator = create(:person) + end + end + + context 'when creating a user with person attributes' do + it 'creates user, person, and community membership successfully' do + expect do + post '/en/users', params: { + user: valid_user_params, + terms_of_service_agreement: '1', + privacy_policy_agreement: '1', + code_of_conduct_agreement: '1' + } + end.to change(BetterTogether::User, :count).by(1) + .and change(BetterTogether::Person, :count).by(1) + .and change( + BetterTogether::PersonCommunityMembership, :count + ).by(1) + .and change( + BetterTogether::AgreementParticipant, :count + ).by(3) + + user = BetterTogether::User.last + expect(user.email).to eq('test@example.com') + expect(user.person).to be_present + expect(user.person.name).to eq('Test User') + expect(user.person.identifier).to eq('test-user') + expect(user.person.person_community_memberships.count).to eq(1) + + user = BetterTogether::User.last + expect(user.email).to eq('test@example.com') + expect(user.person).to be_present + expect(user.person.name).to eq('Test User') + expect(user.person.identifier).to eq('test-user') + expect(user.person.person_community_memberships.count).to eq(1) + end + + it 'handles person validation errors gracefully' do + invalid_params = valid_user_params.dup + invalid_params[:person_attributes][:name] = '' # Invalid empty name + + expect do + post '/en/users', params: { + user: invalid_params, + terms_of_service_agreement: '1', + privacy_policy_agreement: '1', + code_of_conduct_agreement: '1' + } + end.to change(BetterTogether::User, :count).by(1) # User created despite empty name + + expect(response).to have_http_status(:ok) # Form re-rendered with errors + end + end + + context 'when agreements are not accepted' do + it 'does not create user or person' do + expect do + post '/en/users', params: { + user: valid_user_params + # No agreement checkboxes + } + end.not_to change(BetterTogether::User, :count) + + expect(response).to have_http_status(:unprocessable_content) # Unprocessable entity + end + end + end +end diff --git a/spec/support/automatic_test_configuration.rb b/spec/support/automatic_test_configuration.rb index 5a2c2c61a..7314c4b9d 100644 --- a/spec/support/automatic_test_configuration.rb +++ b/spec/support/automatic_test_configuration.rb @@ -93,7 +93,7 @@ def configure_host_platform create( :user, :confirmed, :platform_manager, email: 'manager@example.test', - password: 'password12345' + password: 'SecureTest123!@#' ) end @@ -166,9 +166,9 @@ def use_auth_method_for_spec_type(example, user_type) if controller_spec_type?(example) # Use Devise test helpers for controller specs user = if user_type == :manager - find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) + find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) else - find_or_create_test_user('user@example.test', 'password12345', :user) + find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) end sign_in user elsif feature_spec_type?(example) @@ -176,7 +176,7 @@ def use_auth_method_for_spec_type(example, user_type) extend BetterTogether::CapybaraFeatureHelpers unless respond_to?(:capybara_login_as_platform_manager) # Ensure the target user exists before attempting a UI login if user_type == :manager - find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) + find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) capybara_login_as_platform_manager # Navigate to context-appropriate page when helpful full_description = [ @@ -187,15 +187,15 @@ def use_auth_method_for_spec_type(example, user_type) visit new_conversation_path(locale: I18n.default_locale) end else - find_or_create_test_user('user@example.test', 'password12345', :user) + find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) capybara_login_as_user end else # Request specs: choose auth mechanism based on description user = if user_type == :manager - find_or_create_test_user('manager@example.test', 'password12345', :platform_manager) + find_or_create_test_user('manager@example.test', 'SecureTest123!@#', :platform_manager) else - find_or_create_test_user('user@example.test', 'password12345', :user) + find_or_create_test_user('user@example.test', 'SecureTest123!@#', :user) end full_description = [ @@ -207,7 +207,7 @@ def use_auth_method_for_spec_type(example, user_type) if full_description.include?('Example Automatic Configuration') && respond_to?(:sign_in) sign_in user else - login(user.email, 'password12345') + login(user.email, 'SecureTest123!@#') end end end diff --git a/spec/support/better_together/capybara_feature_helpers.rb b/spec/support/better_together/capybara_feature_helpers.rb index 7fa440e3c..6cb5bfa98 100644 --- a/spec/support/better_together/capybara_feature_helpers.rb +++ b/spec/support/better_together/capybara_feature_helpers.rb @@ -25,7 +25,7 @@ def configure_host_platform create( :user, :confirmed, :platform_manager, email: 'manager@example.test', - password: 'password12345' + password: 'SecureTest123!@#' ) end @@ -34,11 +34,11 @@ def configure_host_platform # rubocop:enable Metrics/MethodLength def capybara_login_as_platform_manager - capybara_sign_in_user('manager@example.test', 'password12345') + capybara_sign_in_user('manager@example.test', 'SecureTest123!@#') end def capybara_login_as_user - capybara_sign_in_user('user@example.test', 'password12345') + capybara_sign_in_user('user@example.test', 'SecureTest123!@#') end # rubocop:todo Metrics/PerceivedComplexity diff --git a/spec/support/better_together/devise_session_helpers.rb b/spec/support/better_together/devise_session_helpers.rb index 5f323218b..fca1beef9 100644 --- a/spec/support/better_together/devise_session_helpers.rb +++ b/spec/support/better_together/devise_session_helpers.rb @@ -25,7 +25,7 @@ def configure_host_platform create( :user, :confirmed, :platform_manager, email: 'manager@example.test', - password: 'password12345' + password: 'SecureTest123!@#' ) end @@ -34,11 +34,11 @@ def configure_host_platform # rubocop:enable Metrics/MethodLength def capybara_login_as_platform_manager - capybara_sign_in_user('manager@example.test', 'password12345') + capybara_sign_in_user('manager@example.test', 'SecureTest123!@#') end def capybara_login_as_user - capybara_sign_in_user('user@example.test', 'password12345') + capybara_sign_in_user('user@example.test', 'SecureTest123!@#') end def capybara_sign_in_user(email, password) diff --git a/spec/support/request_spec_helper.rb b/spec/support/request_spec_helper.rb index d6a6b85f6..7979ba5dc 100644 --- a/spec/support/request_spec_helper.rb +++ b/spec/support/request_spec_helper.rb @@ -90,7 +90,7 @@ def configure_host_platform # rubocop:disable Metrics/MethodLength create( :user, :confirmed, :platform_manager, email: 'manager@example.test', - password: 'password12345' + password: 'SecureTest123!@#' ) end diff --git a/spec/support/test_seed_helpers.rb b/spec/support/test_seed_helpers.rb index 42871b82d..8af4f3f04 100644 --- a/spec/support/test_seed_helpers.rb +++ b/spec/support/test_seed_helpers.rb @@ -8,12 +8,12 @@ unless BetterTogether::User.find_by(email: manager_email) person = BetterTogether::Person.create!(name: 'Manager Person') - BetterTogether::User.create!(email: manager_email, password: 'testpassword12', person: person) + BetterTogether::User.create!(email: manager_email, password: 'SecureTest123!@#', person: person) end unless BetterTogether::User.find_by(email: user_email) person = BetterTogether::Person.create!(name: 'Test User') - BetterTogether::User.create!(email: user_email, password: 'testpassword12', person: person) + BetterTogether::User.create!(email: user_email, password: 'SecureTest123!@#', person: person) end end end