Skip to content

Commit 11541b1

Browse files
authored
Merge pull request #4766 from danielabar/4504-partner-profile-step-form
[#4504] Add step-wise partner profile edit form
2 parents e8dd85d + 1f94522 commit 11541b1

28 files changed

+727
-10
lines changed

app/assets/stylesheets/custom.scss

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,13 @@
9898
margin-top: 40px;
9999
}
100100
}
101+
102+
.accordion-button.saving::after {
103+
background-image: none;
104+
content: "Saving...";
105+
font-size: 0.875rem;
106+
color: #005568;
107+
transform: none;
108+
width: 3rem;
109+
cursor: not-allowed;
110+
}

app/controllers/partners/profiles_controller.rb

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,38 @@ def show; end
55
def edit
66
@counties = County.in_category_name_order
77
@client_share_total = current_partner.profile.client_share_total
8+
9+
if Flipper.enabled?("partner_step_form")
10+
@sections_with_errors = []
11+
render "partners/profiles/step/edit"
12+
else
13+
render "edit"
14+
end
815
end
916

1017
def update
1118
@counties = County.in_category_name_order
1219
result = PartnerProfileUpdateService.new(current_partner, partner_params, profile_params).call
1320
if result.success?
1421
flash[:success] = "Details were successfully updated."
15-
redirect_to partners_profile_path
22+
if Flipper.enabled?("partner_step_form")
23+
if params[:save_review]
24+
redirect_to partners_profile_path
25+
else
26+
redirect_to edit_partners_profile_path
27+
end
28+
else
29+
redirect_to partners_profile_path
30+
end
1631
else
17-
flash[:error] = "There is a problem. Try again: %s" % result.error
18-
render :edit
32+
flash.now[:error] = "There is a problem. Try again: %s" % result.error
33+
if Flipper.enabled?("partner_step_form")
34+
error_keys = current_partner.profile.errors.attribute_names
35+
@sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys)
36+
render "partners/profiles/step/edit"
37+
else
38+
render :edit
39+
end
1940
end
2041
end
2142

app/helpers/partners_helper.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@ def humanize_boolean_3state(boolean)
2727
end
2828
end
2929

30+
# In step-wise editing of the partner profile, the partial name is used as the section header by default.
31+
# This helper allows overriding the header with a custom display name if needed.
32+
def partial_display_name(partial)
33+
custom_names = {
34+
'attached_documents' => 'Additional Documents'
35+
}
36+
37+
custom_names[partial] || partial.humanize
38+
end
39+
40+
def section_with_errors?(section, sections_with_errors = [])
41+
sections_with_errors.include?(section)
42+
end
43+
3044
def partner_status_badge(partner)
3145
if partner.status == "approved"
3246
tag.span partner.display_status, class: %w(badge badge-pill badge-primary bg-primary float-right)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Controller } from "@hotwired/stimulus";
2+
3+
// Connects to data-controller="accordion"
4+
// Intercepts form submission and disables the open/close section buttons.
5+
export default class extends Controller {
6+
static targets = [ "form" ]
7+
8+
disableOpenClose(event) {
9+
event.preventDefault();
10+
11+
const buttons = this.element.querySelectorAll(".accordion-button");
12+
buttons.forEach(button => {
13+
button.disabled = true;
14+
button.classList.add("saving");
15+
});
16+
17+
this.formTarget.requestSubmit();
18+
}
19+
}

app/models/partners/profile.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,23 @@ def client_share_is_0_or_100
142142
# their allocation actually is
143143
total = client_share_total
144144
if total != 0 && total != 100
145-
errors.add(:base, "Total client share must be 0 or 100")
145+
if Flipper.enabled?("partner_step_form")
146+
# need to set errors on specific fields within the form so that it can be mapped to a section
147+
errors.add(:client_share, "Total client share must be 0 or 100")
148+
else
149+
errors.add(:base, "Total client share must be 0 or 100")
150+
end
146151
end
147152
end
148153

149154
def has_at_least_one_request_setting
150155
if !(enable_child_based_requests || enable_individual_requests || enable_quantity_based_requests)
151-
errors.add(:base, "At least one request type must be set")
156+
if Flipper.enabled?("partner_step_form")
157+
# need to set errors on specific fields within the form so that it can be mapped to a section
158+
errors.add(:enable_child_based_requests, "At least one request type must be set")
159+
else
160+
errors.add(:base, "At least one request type must be set")
161+
end
152162
end
153163
end
154164

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module Partners
2+
# SectionErrorService identifies which sections of the Partner Profile step-wise form
3+
# should expand when validation errors occur. This helps users easily locate and fix
4+
# fields with errors in specific sections.
5+
#
6+
# Usage:
7+
# error_keys = [:website, :pick_up_name, :enable_quantity_based_requests]
8+
# sections_with_errors = Partners::SectionErrorService.sections_with_errors(error_keys)
9+
# # => ["media_information", "pick_up_person", "partner_settings"]
10+
#
11+
class SectionErrorService
12+
# Maps form sections to the associated fields (error keys) that belong to them.
13+
SECTION_FIELD_MAPPING = {
14+
media_information: %i[no_social_media_presence website twitter facebook instagram],
15+
partner_settings: %i[enable_child_based_requests enable_individual_requests enable_quantity_based_requests],
16+
pick_up_person: %i[pick_up_email pick_up_name pick_up_phone],
17+
area_served: %i[client_share county_id]
18+
}
19+
20+
# Returns a list of unique sections that contain errors based on the given error keys.
21+
#
22+
# @param error_keys [Array<Symbol>] Array of attribute keys representing the fields with errors.
23+
# @return [Array<String>] An array of section names containing errors.
24+
def self.sections_with_errors(error_keys)
25+
error_keys.flat_map do |key|
26+
SECTION_FIELD_MAPPING.find { |_section, fields| fields.include?(key) }&.first
27+
end.compact.uniq.map(&:to_s)
28+
end
29+
end
30+
end

app/views/layouts/partners/application.html.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<meta http-equiv="X-UA-Compatible" content="IE=edge">
66
<title>Human Essentials</title>
77
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
8+
89
<%= csrf_meta_tags %>
910

1011
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/select2.min.css" rel="stylesheet">
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<%# locals: (f:, partner:, section_id:, section_title:, icon_class:, partial_name:, sections_with_errors:) %>
2+
3+
<div class="accordion-item">
4+
<h2 class="accordion-header">
5+
<button
6+
class="accordion-button <%= section_with_errors?(section_id, sections_with_errors) ? '' : 'collapsed' %>"
7+
type="button" data-bs-toggle="collapse" data-bs-target="#<%= section_id %>"
8+
aria-expanded="<%= section_with_errors?(section_id, sections_with_errors) %>"
9+
aria-controls="<%= section_id %>">
10+
<div class="d-flex justify-content-between align-items-center w-100">
11+
<div class="fs-4">
12+
<i class="fa <%= icon_class %> mr-2"></i>
13+
<%= section_title %>
14+
</div>
15+
</div>
16+
</button>
17+
</h2>
18+
<div id="<%= section_id %>"
19+
class="accordion-collapse collapse <%= section_with_errors?(section_id, sections_with_errors) ? 'show' : '' %>">
20+
<div class="accordion-body">
21+
<%= render "partners/profiles/step/#{partial_name}_form" , f: f, partner: partner, profile: partner.profile %>
22+
</div>
23+
</div>
24+
</div>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<%= f.fields_for :profile, profile do |pf| %>
2+
<h3><strong>Agency Distribution Information</strong></h3>
3+
4+
<div class="form-group">
5+
<%= pf.input :distribution_times, label: "Distribution Times", class: "form-control" %>
6+
</div>
7+
8+
<div class="form-group">
9+
<%= pf.input :new_client_times, label: "New Client Times", class: "form-control" %>
10+
</div>
11+
12+
<div class="form-group">
13+
<%= pf.input :more_docs_required, label: "More Docs Required", class: "form-control" %>
14+
</div>
15+
<% end %>
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<div class="form-group">
2+
<%= f.input :name, label: "Agency Name", required: true, class: "form-control" %>
3+
</div>
4+
5+
<%= f.fields_for :profile, profile do |pf| %>
6+
<div class="form-group">
7+
<%= pf.input :agency_type, collection: Partner::AGENCY_TYPES.values, label: "Agency Type", class: "form-control", wrapper: :input_group %>
8+
</div>
9+
10+
<div class="form-group">
11+
<%= pf.input :other_agency_type, label: "Other Agency Type", class: "form-control" %>
12+
</div>
13+
14+
<div class="form-group row">
15+
<label class="control-label col-md-3">501(c)(3) IRS Determination Letter</label>
16+
<% if profile.proof_of_partner_status.attached? %>
17+
<div class="col-md-8">
18+
Attached file: <%= link_to profile.proof_of_partner_status.blob['filename'], rails_blob_path(profile.proof_of_partner_status), class: "font-weight-bold" %>
19+
<%= pf.file_field :proof_of_partner_status, class: "form-control-file" %>
20+
</div>
21+
<% else %>
22+
<div class="col-md-8">
23+
<%= pf.file_field :proof_of_partner_status, class: "form-control-file" %>
24+
</div>
25+
<% end %>
26+
</div>
27+
28+
<div class="form-group">
29+
<%= pf.input :agency_mission, as: :text, label: "Agency Mission", class: "form-control" %>
30+
</div>
31+
32+
<div class="form-group">
33+
<%= pf.input :address1, label: "Address (line 1)", class: "form-control" %>
34+
</div>
35+
36+
<div class="form-group">
37+
<%= pf.input :address2, label: "Address (line 2)", class: "form-control" %>
38+
</div>
39+
40+
<div class="form-group">
41+
<%= pf.input :city, label: "City", class: "form-control" %>
42+
</div>
43+
44+
<div class="form-group">
45+
<%= pf.input :state, label: "State", class: "form-control" %>
46+
</div>
47+
48+
<div class="form-group">
49+
<%= pf.input :zip_code, label: "Zip Code", class: "form-control" %>
50+
</div>
51+
<% end %>

0 commit comments

Comments
 (0)