diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss index ad5a229332..83ec2d042c 100644 --- a/app/assets/stylesheets/custom.scss +++ b/app/assets/stylesheets/custom.scss @@ -98,3 +98,13 @@ margin-top: 40px; } } + +.accordion-button.saving::after { + background-image: none; + content: "Saving..."; + font-size: 0.875rem; + color: #005568; + transform: none; + width: 3rem; + cursor: not-allowed; +} diff --git a/app/controllers/partners/profiles_controller.rb b/app/controllers/partners/profiles_controller.rb index d6508f7fa4..e92085c037 100644 --- a/app/controllers/partners/profiles_controller.rb +++ b/app/controllers/partners/profiles_controller.rb @@ -5,6 +5,13 @@ def show; end def edit @counties = County.in_category_name_order @client_share_total = current_partner.profile.client_share_total + + if Flipper.enabled?("partner_step_form") + @sections_with_errors = [] + render "partners/profiles/step/edit" + else + render "edit" + end end def update @@ -12,10 +19,24 @@ def update result = PartnerProfileUpdateService.new(current_partner, partner_params, profile_params).call if result.success? flash[:success] = "Details were successfully updated." - redirect_to partners_profile_path + if Flipper.enabled?("partner_step_form") + if params[:save_review] + redirect_to partners_profile_path + else + redirect_to edit_partners_profile_path + end + else + redirect_to partners_profile_path + end else - flash[:error] = "There is a problem. Try again: %s" % result.error - render :edit + flash.now[:error] = "There is a problem. Try again: %s" % result.error + if Flipper.enabled?("partner_step_form") + error_keys = current_partner.profile.errors.attribute_names + @sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys) + render "partners/profiles/step/edit" + else + render :edit + end end end diff --git a/app/helpers/partners_helper.rb b/app/helpers/partners_helper.rb index fcf9f498c9..74974649f3 100644 --- a/app/helpers/partners_helper.rb +++ b/app/helpers/partners_helper.rb @@ -27,6 +27,20 @@ def humanize_boolean_3state(boolean) end end + # In step-wise editing of the partner profile, the partial name is used as the section header by default. + # This helper allows overriding the header with a custom display name if needed. + def partial_display_name(partial) + custom_names = { + 'attached_documents' => 'Additional Documents' + } + + custom_names[partial] || partial.humanize + end + + def section_with_errors?(section, sections_with_errors = []) + sections_with_errors.include?(section) + end + def partner_status_badge(partner) if partner.status == "approved" tag.span partner.display_status, class: %w(badge badge-pill badge-primary bg-primary float-right) diff --git a/app/javascript/controllers/accordion_controller.js b/app/javascript/controllers/accordion_controller.js new file mode 100644 index 0000000000..def5ebeaa5 --- /dev/null +++ b/app/javascript/controllers/accordion_controller.js @@ -0,0 +1,19 @@ +import { Controller } from "@hotwired/stimulus"; + +// Connects to data-controller="accordion" +// Intercepts form submission and disables the open/close section buttons. +export default class extends Controller { + static targets = [ "form" ] + + disableOpenClose(event) { + event.preventDefault(); + + const buttons = this.element.querySelectorAll(".accordion-button"); + buttons.forEach(button => { + button.disabled = true; + button.classList.add("saving"); + }); + + this.formTarget.requestSubmit(); + } +} diff --git a/app/models/partners/profile.rb b/app/models/partners/profile.rb index b2806db5bf..3fd9fcb397 100644 --- a/app/models/partners/profile.rb +++ b/app/models/partners/profile.rb @@ -142,13 +142,23 @@ def client_share_is_0_or_100 # their allocation actually is total = client_share_total if total != 0 && total != 100 - errors.add(:base, "Total client share must be 0 or 100") + if Flipper.enabled?("partner_step_form") + # need to set errors on specific fields within the form so that it can be mapped to a section + errors.add(:client_share, "Total client share must be 0 or 100") + else + errors.add(:base, "Total client share must be 0 or 100") + end end end def has_at_least_one_request_setting if !(enable_child_based_requests || enable_individual_requests || enable_quantity_based_requests) - errors.add(:base, "At least one request type must be set") + if Flipper.enabled?("partner_step_form") + # need to set errors on specific fields within the form so that it can be mapped to a section + errors.add(:enable_child_based_requests, "At least one request type must be set") + else + errors.add(:base, "At least one request type must be set") + end end end diff --git a/app/services/partners/section_error_service.rb b/app/services/partners/section_error_service.rb new file mode 100644 index 0000000000..5a6ff006b2 --- /dev/null +++ b/app/services/partners/section_error_service.rb @@ -0,0 +1,30 @@ +module Partners + # SectionErrorService identifies which sections of the Partner Profile step-wise form + # should expand when validation errors occur. This helps users easily locate and fix + # fields with errors in specific sections. + # + # Usage: + # error_keys = [:website, :pick_up_name, :enable_quantity_based_requests] + # sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys) + # # => ["media_information", "pick_up_person", "partner_settings"] + # + class SectionErrorService + # Maps form sections to the associated fields (error keys) that belong to them. + SECTION_FIELD_MAPPING = { + media_information: %i[no_social_media_presence website twitter facebook instagram], + partner_settings: %i[enable_child_based_requests enable_individual_requests enable_quantity_based_requests], + pick_up_person: %i[pick_up_email pick_up_name pick_up_phone], + area_served: %i[client_share county_id] + } + + # Returns a list of unique sections that contain errors based on the given error keys. + # + # @param error_keys [Array] Array of attribute keys representing the fields with errors. + # @return [Array] An array of section names containing errors. + def self.sections_with_errors(error_keys) + error_keys.flat_map do |key| + SECTION_FIELD_MAPPING.find { |_section, fields| fields.include?(key) }&.first + end.compact.uniq.map(&:to_s) + end + end +end diff --git a/app/views/layouts/partners/application.html.erb b/app/views/layouts/partners/application.html.erb index ac226661c4..b1e6f1129a 100644 --- a/app/views/layouts/partners/application.html.erb +++ b/app/views/layouts/partners/application.html.erb @@ -5,6 +5,7 @@ Human Essentials + <%= csrf_meta_tags %> diff --git a/app/views/partners/profiles/step/_accordion_section.html.erb b/app/views/partners/profiles/step/_accordion_section.html.erb new file mode 100644 index 0000000000..382af16521 --- /dev/null +++ b/app/views/partners/profiles/step/_accordion_section.html.erb @@ -0,0 +1,24 @@ +<%# locals: (f:, partner:, section_id:, section_title:, icon_class:, partial_name:, sections_with_errors:) %> + +
+

+ +

+
+
+ <%= render "partners/profiles/step/#{partial_name}_form" , f: f, partner: partner, profile: partner.profile %> +
+
+
diff --git a/app/views/partners/profiles/step/_agency_distribution_information_form.html.erb b/app/views/partners/profiles/step/_agency_distribution_information_form.html.erb new file mode 100644 index 0000000000..8a221dad05 --- /dev/null +++ b/app/views/partners/profiles/step/_agency_distribution_information_form.html.erb @@ -0,0 +1,15 @@ +<%= f.fields_for :profile, profile do |pf| %> +

Agency Distribution Information

+ +
+ <%= pf.input :distribution_times, label: "Distribution Times", class: "form-control" %> +
+ +
+ <%= pf.input :new_client_times, label: "New Client Times", class: "form-control" %> +
+ +
+ <%= pf.input :more_docs_required, label: "More Docs Required", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_agency_information_form.html.erb b/app/views/partners/profiles/step/_agency_information_form.html.erb new file mode 100644 index 0000000000..0a65397daf --- /dev/null +++ b/app/views/partners/profiles/step/_agency_information_form.html.erb @@ -0,0 +1,51 @@ +
+ <%= f.input :name, label: "Agency Name", required: true, class: "form-control" %> +
+ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :agency_type, collection: Partner::AGENCY_TYPES.values, label: "Agency Type", class: "form-control", wrapper: :input_group %> +
+ +
+ <%= pf.input :other_agency_type, label: "Other Agency Type", class: "form-control" %> +
+ +
+ + <% if profile.proof_of_partner_status.attached? %> +
+ Attached file: <%= link_to profile.proof_of_partner_status.blob['filename'], rails_blob_path(profile.proof_of_partner_status), class: "font-weight-bold" %> + <%= pf.file_field :proof_of_partner_status, class: "form-control-file" %> +
+ <% else %> +
+ <%= pf.file_field :proof_of_partner_status, class: "form-control-file" %> +
+ <% end %> +
+ +
+ <%= pf.input :agency_mission, as: :text, label: "Agency Mission", class: "form-control" %> +
+ +
+ <%= pf.input :address1, label: "Address (line 1)", class: "form-control" %> +
+ +
+ <%= pf.input :address2, label: "Address (line 2)", class: "form-control" %> +
+ +
+ <%= pf.input :city, label: "City", class: "form-control" %> +
+ +
+ <%= pf.input :state, label: "State", class: "form-control" %> +
+ +
+ <%= pf.input :zip_code, label: "Zip Code", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_agency_stability_form.html.erb b/app/views/partners/profiles/step/_agency_stability_form.html.erb new file mode 100644 index 0000000000..5368b49bfc --- /dev/null +++ b/app/views/partners/profiles/step/_agency_stability_form.html.erb @@ -0,0 +1,52 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :founded, label: "Year Founded", class: "form-control" %> +
+ +
+ <%= pf.input :form_990, label: "Form 990 Filed", as: :radio_buttons, class: "form-control" %> +
+ + <% if profile.proof_of_form_990.attached? %> +
+ + <%= link_to profile.proof_of_form_990.blob['filename'], rails_blob_path(profile.proof_of_form_990), class: "font-weight-bold" %> +
+ <% end %> + +
+ <%= pf.file_field :proof_of_form_990, class: "form-control-file" %> +
+ +
+ <%= pf.input :program_name, label: "Program Name(s)", class: "form-control" %> +
+ +
+ <%= pf.input :program_description, label: "Program Description(s)", class: "form-control" %> +
+ +
+ <%= pf.input :program_age, label: "Agency Age", class: "form-control" %> +
+ +
+ <%= pf.input :evidence_based, label: "Evidence Based?", as: :radio_buttons, class: "form-control" %> +
+ +
+ <%= pf.input :case_management, label: "Case Management", as: :radio_buttons, class: "form-control" %> +
+ +
+ <%= pf.input :essentials_use, label: "How Are Essentials (e.g. diapers, period supplies) Used In Your Program?", as: :text, class: "form-control" %> +
+ +
+ <%= pf.input :receives_essentials_from_other, label: "Do You Receive Essentials From Other Sources?", class: "form-control" %> +
+ +
+ <%= pf.input :currently_provide_diapers, label: "Currently Providing Diapers?", as: :radio_buttons, class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_area_served_form.html.erb b/app/views/partners/profiles/step/_area_served_form.html.erb new file mode 100644 index 0000000000..05147673a1 --- /dev/null +++ b/app/views/partners/profiles/step/_area_served_form.html.erb @@ -0,0 +1,26 @@ +<%= f.simple_fields_for :profile, profile do |pf| %> +
+
+ +
+ <%= render 'served_areas/served_area_fields', form: pf %> +
+ +
+
Total is:
+ + <%= @client_share_total %> % + +
+ The total client share must be either 0 or 100 %. +
+
+ + +
+
+<% end %> diff --git a/app/views/partners/profiles/step/_attached_documents_form.html.erb b/app/views/partners/profiles/step/_attached_documents_form.html.erb new file mode 100644 index 0000000000..9164809733 --- /dev/null +++ b/app/views/partners/profiles/step/_attached_documents_form.html.erb @@ -0,0 +1,15 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <% if profile.documents.attached? %> + Attached files: +
    + <% profile.documents.each do |doc| %> +
  • <%= link_to doc.blob['filename'], rails_blob_path(doc), class: "font-weight-bold" %>
  • + <% end %> +
+ <%= pf.file_field :documents, multiple: true, class: "form-control-file" %> + <% else %> + <%= pf.file_field :documents, multiple: true, class: "form-control-file" %> + <% end %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_executive_director_form.html.erb b/app/views/partners/profiles/step/_executive_director_form.html.erb new file mode 100644 index 0000000000..2cb5e963dd --- /dev/null +++ b/app/views/partners/profiles/step/_executive_director_form.html.erb @@ -0,0 +1,26 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :executive_director_name, label: "Executive Director Name", class: "form-control" %> +
+
+ <%= pf.input :executive_director_phone, label: "Executive Director Phone", class: "form-control" %> +
+
+ <%= pf.input :executive_director_email, label: "Executive Director Email", class: "form-control" %> +
+ +

Primary Contact

+ +
+ <%= pf.input :primary_contact_name, label: "Primary Contact Name", class: "form-control" %> +
+
+ <%= pf.input :primary_contact_phone, label: "Primary Contact Phone", class: "form-control" %> +
+
+ <%= pf.input :primary_contact_mobile, label: "Primary Contact Cell", class: "form-control" %> +
+
+ <%= pf.input :primary_contact_email, label: "Primary Contact Email", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_form_actions.html.erb b/app/views/partners/profiles/step/_form_actions.html.erb new file mode 100644 index 0000000000..a98860c120 --- /dev/null +++ b/app/views/partners/profiles/step/_form_actions.html.erb @@ -0,0 +1,6 @@ +<%# locals: (f:, partner:) %> + +
+ <%= f.submit "Save Progress", class: 'btn btn-primary mr-2', data: { action: "click->accordion#disableOpenClose" } %> + <%= f.submit "Save and Review", name: "save_review", class: 'btn btn-success' %> +
diff --git a/app/views/partners/profiles/step/_media_information_form.html.erb b/app/views/partners/profiles/step/_media_information_form.html.erb new file mode 100644 index 0000000000..4104d39ec1 --- /dev/null +++ b/app/views/partners/profiles/step/_media_information_form.html.erb @@ -0,0 +1,22 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :website, label: "Website", class: "form-control" %> +
+ +
+ <%= pf.input :facebook, label: "Facebook", class: "form-control" %> +
+ +
+ <%= pf.input :twitter, label: "Twitter", class: "form-control" %> +
+ +
+ <%= pf.input :instagram, label: "Instagram", class: "form-control" %> +
+ +
+ <%= pf.check_box :no_social_media_presence, as: :boolean %>  + <%= pf.label :no_social_media_presence, "No Social Media Presence" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_organizational_capacity_form.html.erb b/app/views/partners/profiles/step/_organizational_capacity_form.html.erb new file mode 100644 index 0000000000..8321820890 --- /dev/null +++ b/app/views/partners/profiles/step/_organizational_capacity_form.html.erb @@ -0,0 +1,13 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :client_capacity, label: "Client Capacity", class: "form-control" %> +
+ +
+ <%= pf.input :storage_space, label: "Storage Space", as: :radio_buttons, class: "form-control" %> +
+ +
+ <%= pf.input :describe_storage_space, label: "Storage Space Description", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_partner_settings_form.html.erb b/app/views/partners/profiles/step/_partner_settings_form.html.erb new file mode 100644 index 0000000000..7ffa36a1d5 --- /dev/null +++ b/app/views/partners/profiles/step/_partner_settings_form.html.erb @@ -0,0 +1,22 @@ +<%= f.fields_for :profile, profile do |pf| %> + <% if pf.object.organization.enable_quantity_based_requests? %> +
+ <%= pf.check_box :enable_quantity_based_requests, as: :boolean %>  + <%= pf.label :enable_quantity_based_requests, "Enable Quantity-based Requests" %> +
+ <% end %> + + <% if pf.object.organization.enable_child_based_requests? %> +
+ <%= pf.check_box :enable_child_based_requests, as: :boolean %>  + <%= pf.label :enable_child_based_requests, "Enable Child-based Requests (unclick if you only do bulk requests)" %> +
+ <% end %> + + <% if pf.object.organization.enable_individual_requests? %> +
+ <%= pf.check_box :enable_individual_requests, as: :boolean %>  + <%= pf.label :enable_individual_requests, "Enable Requests for Individuals" %> +
+ <% end %> +<% end %> diff --git a/app/views/partners/profiles/step/_pick_up_person_form.html.erb b/app/views/partners/profiles/step/_pick_up_person_form.html.erb new file mode 100644 index 0000000000..192a94466f --- /dev/null +++ b/app/views/partners/profiles/step/_pick_up_person_form.html.erb @@ -0,0 +1,11 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :pick_up_name, label: "Pick Up Person Name", class: "form-control" %> +
+
+ <%= pf.input :pick_up_phone, label: "Pick Up Person's Phone #", class: "form-control" %> +
+
+ <%= pf.input :pick_up_email, label: "Pick Up Person's Email", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_population_served_form.html.erb b/app/views/partners/profiles/step/_population_served_form.html.erb new file mode 100644 index 0000000000..e42e1298fd --- /dev/null +++ b/app/views/partners/profiles/step/_population_served_form.html.erb @@ -0,0 +1,58 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :income_requirement_desc, label: "Clients Have An Income Requirement to Work With You?", + as: :radio_buttons, class: "form-control", wrapper: :input_group, + wrapper_html: {class: "form-yesno"}, input_html: {class: "radio-yesno"} %> +
+ +
+ <%= pf.input :income_verification, label: "Do You Verify The Income Of Your Clients?", + as: :radio_buttons, class: "form-control", wrapper: :input_group, + wrapper_html: {class: "form-yesno"}, input_html: {class: "radio-yesno"} %> +
+ +

Race/Ethnicity of Client Base

+ +
+ <%= pf.input :population_black, label: "% African American", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_white, label: "% Caucasian", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_hispanic, label: "% Hispanic", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_asian, label: "% Asian", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_american_indian, label: "% American Indian", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_island, label: "% Pacific Island", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_multi_racial, label: "% Multi-racial", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :population_other, label: "% Other", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :zips_served, label: "Zip Codes Served", class: "form-control", wrapper: :input_group %> +
+ +

Poverty Information of Those Served

+ +
+ <%= pf.input :at_fpl_or_below, label: "% At FPL or Below", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :above_1_2_times_fpl, label: "% Above 1-2 times FPL", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :greater_2_times_fpl, label: "% Greater than 2 times FPL", as: :integer, class: "form-control", wrapper: :input_group %> +
+
+ <%= pf.input :poverty_unknown, label: "% Poverty Unknown", as: :integer, class: "form-control", wrapper: :input_group %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_program_delivery_address_form.html.erb b/app/views/partners/profiles/step/_program_delivery_address_form.html.erb new file mode 100644 index 0000000000..f47f9fa272 --- /dev/null +++ b/app/views/partners/profiles/step/_program_delivery_address_form.html.erb @@ -0,0 +1,21 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :program_address1, label: "Address (line 1)", class: "form-control" %> +
+ +
+ <%= pf.input :program_address2, label: "Address (line 2)", class: "form-control" %> +
+ +
+ <%= pf.input :program_city, label: "City", class: "form-control" %> +
+ +
+ <%= pf.input :program_state, label: "State", class: "form-control" %> +
+ +
+ <%= pf.input :program_zip_code, label: "Zip Code", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/_sources_of_funding_form.html.erb b/app/views/partners/profiles/step/_sources_of_funding_form.html.erb new file mode 100644 index 0000000000..aeacdfba47 --- /dev/null +++ b/app/views/partners/profiles/step/_sources_of_funding_form.html.erb @@ -0,0 +1,17 @@ +<%= f.fields_for :profile, profile do |pf| %> +
+ <%= pf.input :sources_of_funding, label: "Sources Of Funding", class: "form-control" %> +
+ +
+ <%= pf.input :sources_of_diapers, label: "How do you currently obtain diapers?", class: "form-control" %> +
+ +
+ <%= pf.input :essentials_budget, label: "Essentials Budget", class: "form-control" %> +
+ +
+ <%= pf.input :essentials_funding_source, label: "Essentials Funding Source", class: "form-control" %> +
+<% end %> diff --git a/app/views/partners/profiles/step/edit.html.erb b/app/views/partners/profiles/step/edit.html.erb new file mode 100644 index 0000000000..513a53b3fe --- /dev/null +++ b/app/views/partners/profiles/step/edit.html.erb @@ -0,0 +1,46 @@ +
+
+
+
+ <% content_for :title, "Step Editing - #{current_partner.name}" %> +

  Edit My Organization    + <%= partner_status_badge(current_partner) %> + for <%= current_partner.name %> +

+
+ +
+
+
+ + + +
+ <%= simple_form_for current_partner, + data: { controller: "form-input", accordion_target: "form" }, + url: partners_profile_path, + html: { multipart: true } do |f| %> + + <%= render 'partners/profiles/step/form_actions', f: f, partner: current_partner %> + +
+ <%= render 'partners/profiles/step/accordion_section', f: f, partner: current_partner, section_id: 'agency_information', section_title: 'Agency Information', icon_class: 'fa-edit', partial_name: 'agency_information', sections_with_errors: @sections_with_errors %> + <%= render 'partners/profiles/step/accordion_section', f: f, partner: current_partner, section_id: 'program_delivery_address', section_title: 'Program / Delivery Address', icon_class: 'fa-map', partial_name: 'program_delivery_address', sections_with_errors: @sections_with_errors %> + <% current_partner.partials_to_show.each do |partial| %> + <%= render 'partners/profiles/step/accordion_section', f: f, partner: current_partner, section_id: partial, section_title: partial_display_name(partial), icon_class: 'fa-cogs', partial_name: partial, sections_with_errors: @sections_with_errors %> + <% end %> + <%= render 'partners/profiles/step/accordion_section', f: f, partner: current_partner, section_id: 'partner_settings', section_title: 'Settings', icon_class: 'fa-cog', partial_name: 'partner_settings', sections_with_errors: @sections_with_errors %> +
+ + <%= render 'partners/profiles/step/form_actions', f: f, partner: current_partner %> + <% end %> +
diff --git a/app/views/served_areas/_served_area_fields.html.erb b/app/views/served_areas/_served_area_fields.html.erb index 2d5866fdb5..124261ecc1 100644 --- a/app/views/served_areas/_served_area_fields.html.erb +++ b/app/views/served_areas/_served_area_fields.html.erb @@ -1,14 +1,14 @@ -<%= form.fields_for :served_areas, defined?(object) ? object : nil do |f| %> +<%= form.fields_for :served_areas, defined?(object) ? object : nil do |fsa| %>
- <%= f.input :county_id, collection: @counties, prompt: "County or equivalent", include_blank: "", + <%= fsa.input :county_id, collection: @counties, prompt: "County or equivalent", include_blank: "", label: false, input_html: { class: "my-0 pc-county-select", "data-controller": "select2" } %>
- <%= f.input :client_share, collection: 1..100, placeholder: "Client Share", label: false, input_html: { + <%= fsa.input :client_share, collection: 1..100, placeholder: "Client Share", label: false, input_html: { class: "pc-client-share percentage-selector", "data-area-served-target": "share", "data-served-area-target": "share", "data-action": "change->area-served#calculateClientShareTotal keyup->area-served#calculateClientShareTotal" } %> diff --git a/spec/helpers/partners_helper_spec.rb b/spec/helpers/partners_helper_spec.rb new file mode 100644 index 0000000000..f9c2b46131 --- /dev/null +++ b/spec/helpers/partners_helper_spec.rb @@ -0,0 +1,11 @@ +describe PartnersHelper, type: :helper do + describe "partial_display_name" do + it "returns the humanized name by default" do + expect(helper.partial_display_name("agency_stability")).to eq("Agency stability") + end + + it "returns the custom display name when overridden" do + expect(helper.partial_display_name("attached_documents")).to eq("Additional Documents") + end + end +end diff --git a/spec/models/partners/profile_spec.rb b/spec/models/partners/profile_spec.rb index 76aaac100e..80a6cb0c05 100644 --- a/spec/models/partners/profile_spec.rb +++ b/spec/models/partners/profile_spec.rb @@ -97,10 +97,19 @@ subject { build(:partner_profile, enable_child_based_requests: false, enable_individual_requests: false, enable_quantity_based_requests: false) } context "no settings are set to true" do - it "should not be valid" do + it "sets error at base when feature flag disabled for partner step form" do + allow(Flipper).to receive(:enabled?).with("partner_step_form").and_return(false) + expect(subject).to_not be_valid expect(subject.errors[:base]).to include("At least one request type must be set") end + + it "sets error at field level when feature flag enabled for partner step form" do + allow(Flipper).to receive(:enabled?).with("partner_step_form").and_return(true) + + expect(subject).to_not be_valid + expect(subject.errors[:enable_child_based_requests]).to include("At least one request type must be set") + end end context "at least one request type is set to true" do @@ -256,7 +265,9 @@ end context "multiple" do - it "sums the client shares " do + it "sums the client shares and sets error at base when feature flag disabled for partner step form" do + allow(Flipper).to receive(:enabled?).with("partner_step_form").and_return(false) + profile = create(:partner_profile) county1 = create(:county, name: "county1", region: "region1") county2 = create(:county, name: "county2", region: "region2") @@ -265,6 +276,22 @@ profile.reload expect(profile.client_share_total).to eq(99) expect(profile.valid?).to eq(false) + expect(profile.errors[:base]).to include("Total client share must be 0 or 100") + end + + it "sets error at field level when feature flag enabled for partner step form" do + allow(Flipper).to receive(:enabled?).with("partner_step_form").and_return(true) + + profile = create(:partner_profile) + county1 = create(:county, name: "county1", region: "region1") + county2 = create(:county, name: "county2", region: "region2") + create(:partners_served_area, partner_profile: profile, county: county1, client_share: 50) + create(:partners_served_area, partner_profile: profile, county: county2, client_share: 49) + profile.reload + + expect(profile.client_share_total).to eq(99) + expect(profile.valid?).to eq(false) + expect(profile.errors[:client_share]).to include("Total client share must be 0 or 100") end it "is valid if client share sum is 100" do diff --git a/spec/services/partners/section_error_service_spec.rb b/spec/services/partners/section_error_service_spec.rb new file mode 100644 index 0000000000..b2c6b3435b --- /dev/null +++ b/spec/services/partners/section_error_service_spec.rb @@ -0,0 +1,47 @@ +require "rails_helper" + +RSpec.describe Partners::SectionErrorService, type: :service do + describe ".sections_with_errors" do + subject { Partners::SectionErrorService.sections_with_errors(error_keys) } + + context "when error keys map to multiple sections" do + let(:error_keys) { [:website, :pick_up_email, :enable_quantity_based_requests] } + + it "returns an array with each section containing an error" do + expect(subject).to contain_exactly("media_information", "pick_up_person", "partner_settings") + end + end + + context "when error keys map to the same section multiple times" do + let(:error_keys) { [:website, :twitter, :facebook] } + + it "returns a unique array with only one instance of the section" do + expect(subject).to eq(["media_information"]) + end + end + + context "when error keys include fields not mapped to any section" do + let(:error_keys) { [:website, :unknown_field, :enable_quantity_based_requests] } + + it "excludes nil values for unmapped fields and returns unique sections" do + expect(subject).to eq(["media_information", "partner_settings"]) + end + end + + context "when none of the error keys match any section" do + let(:error_keys) { [:unknown_field_1, :unknown_field_2] } + + it "returns an empty array when no sections match" do + expect(subject).to be_empty + end + end + + context "when error keys are empty" do + let(:error_keys) { [] } + + it "returns an empty array" do + expect(subject).to eq([]) + end + end + end +end diff --git a/spec/system/partners/profile_edit_system_spec.rb b/spec/system/partners/profile_edit_system_spec.rb new file mode 100644 index 0000000000..3d35a5e580 --- /dev/null +++ b/spec/system/partners/profile_edit_system_spec.rb @@ -0,0 +1,102 @@ +RSpec.describe "Partners profile edit", type: :system, js: true do + let!(:partner1) { create(:partner, status: "invited") } + let(:partner1_user) { partner1.primary_user } + + context "step-wise editing is enabled" do + before do + Flipper.enable(:partner_step_form) + login_as(partner1_user) + visit edit_partners_profile_path + end + + it "displays all sections in a closed state by default" do + within ".accordion" do + expect(page).to have_css("#agency_information.accordion-collapse.collapse", visible: false) + expect(page).to have_css("#program_delivery_address.accordion-collapse.collapse", visible: false) + + partner1.partials_to_show.each do |partial| + expect(page).to have_css("##{partial}.accordion-collapse.collapse", visible: false) + end + + expect(page).to have_css("#partner_settings.accordion-collapse.collapse", visible: false) + end + end + + it "allows sections to be opened, closed, filled in any order, and submit for approval" do + # Media + find("button[data-bs-target='#media_information']").click + expect(page).to have_css("#media_information.accordion-collapse.collapse.show", visible: true) + within "#media_information" do + fill_in "Website", with: "https://www.example.com" + end + find("button[data-bs-target='#media_information']").click + expect(page).to have_css("#media_information.accordion-collapse.collapse", visible: false) + + # Executive director + find("button[data-bs-target='#executive_director']").click + expect(page).to have_css("#executive_director.accordion-collapse.collapse.show", visible: true) + within "#executive_director" do + fill_in "Executive Director Name", with: "Lisa Smith" + end + + # Save Progress + all("input[type='submit'][value='Save Progress']").last.click + expect(page).to have_css(".alert-success", text: "Details were successfully updated.") + + # Submit and Review + all("input[type='submit'][value='Save and Review']").last.click + expect(current_path).to eq(partners_profile_path) + expect(page).to have_css(".alert-success", text: "Details were successfully updated.") + end + + it "displays the edit view with sections containing validation errors expanded" do + # Open up Media section and clear out website value + find("button[data-bs-target='#media_information']").click + within "#media_information" do + fill_in "Website", with: "" + end + + # Open Pick up person section and fill in 4 email addresses + find("button[data-bs-target='#pick_up_person']").click + within "#pick_up_person" do + fill_in "Pick Up Person's Email", with: "email1@example.com, email2@example.com, email3@example.com, email4@example.com" + end + + # Open Partner Settings section and uncheck all options + find("button[data-bs-target='#partner_settings']").click + within "#partner_settings" do + uncheck "Enable Quantity-based Requests" if has_checked_field?("Enable Quantity-based Requests") + uncheck "Enable Child-based Requests (unclick if you only do bulk requests)" if has_checked_field?("Enable Child-based Requests (unclick if you only do bulk requests)") + uncheck "Enable Requests for Individuals" if has_checked_field?("Enable Requests for Individuals") + end + + # Save Progress + all("input[type='submit'][value='Save Progress']").last.click + + # Expect an alert-danger message containing validation errors + expect(page).to have_css(".alert-danger", text: /There is a problem/) + expect(page).to have_content("No social media presence must be checked if you have not provided any of Website, Twitter, Facebook, or Instagram.") + expect(page).to have_content("Enable child based requests At least one request type must be set") + expect(page).to have_content("Pick up email can't have more than three email addresses") + + # Expect media section, executive director section, and partner settings section to be opened + expect(page).to have_css("#media_information.accordion-collapse.collapse.show", visible: true) + expect(page).to have_css("#pick_up_person.accordion-collapse.collapse.show", visible: true) + expect(page).to have_css("#partner_settings.accordion-collapse.collapse.show", visible: true) + + # Try to Submit and Review from error state + all("input[type='submit'][value='Save and Review']").last.click + + # Expect an alert-danger message containing validation errors + expect(page).to have_css(".alert-danger", text: /There is a problem/) + expect(page).to have_content("No social media presence must be checked if you have not provided any of Website, Twitter, Facebook, or Instagram.") + expect(page).to have_content("Enable child based requests At least one request type must be set") + expect(page).to have_content("Pick up email can't have more than three email addresses") + + # Expect media section, executive director section, and partner settings section to be opened + expect(page).to have_css("#media_information.accordion-collapse.collapse.show", visible: true) + expect(page).to have_css("#pick_up_person.accordion-collapse.collapse.show", visible: true) + expect(page).to have_css("#partner_settings.accordion-collapse.collapse.show", visible: true) + end + end +end