Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions app/controllers/story_ideas_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ def set_form_variables
@organizations = (@user || current_user)&.organizations&.order(:name) || Organization.none
@windows_types = WindowsType.all

@workshops = authorized_scope(Workshop.all).includes(:windows_type).order(:title)
# Create a special "New Workshop" option
new_workshop_option = OpenStruct.new(id: "new", type_name: "New Workshop")
@workshops = [ new_workshop_option ] + authorized_scope(Workshop.all).includes(:windows_type).order(:title).to_a

users = authorized_scope(User.has_access.includes(:person))
users = users.or(User.where(id: @story_idea.created_by_id)) if @story_idea&.created_by_id
Expand Down Expand Up @@ -156,7 +158,7 @@ def set_story_idea
end

def story_idea_params
params.require(:story_idea).permit(
permitted_params = params.require(:story_idea).permit(
:title, :body, :youtube_url,
:permission_given, :publish_preferences, :promoted_to_story,
:windows_type_id, :organization_id, :workshop_id, :external_workshop_title,
Expand All @@ -166,5 +168,12 @@ def story_idea_params
primary_asset_attributes: [ :id, :file, :_destroy ],
gallery_assets_attributes: [ :id, :file, :_destroy ]
)

# Clear workshop_id if "new" was selected (triggers external_workshop_title)
if permitted_params[:workshop_id] == "new"
permitted_params[:workshop_id] = nil
end

permitted_params
end
end
47 changes: 47 additions & 0 deletions app/frontend/javascript/controllers/workshop_toggle_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Controller } from "@hotwired/stimulus";

// Connects to data-controller="workshop-toggle"
// Handles toggling between workshop dropdown and external title field
export default class extends Controller {
static targets = ["dropdown", "externalField"];

connect() {
this.checkInitialState();
}

checkInitialState() {
const select = this.dropdownTarget.querySelector("select");
if (select && select.value === "new") {
this.showExternalField();
}
}

handleChange(event) {
const value = event.target.value;
if (value === "new") {
this.showExternalField();
}
}

showExternalField() {
this.dropdownTarget.classList.add("hidden");
this.externalFieldTarget.classList.remove("hidden");
}

showDropdown() {
this.externalFieldTarget.classList.add("hidden");
this.dropdownTarget.classList.remove("hidden");

// Clear the selection back to prompt
const select = this.dropdownTarget.querySelector("select");
if (select) {
select.value = "";
}

// Clear the external workshop title field
const externalField = this.externalFieldTarget.querySelector("input, textarea");
if (externalField) {
externalField.value = "";
}
}
}
13 changes: 13 additions & 0 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,17 @@
]
ActiveSupport::TimeZone.us_zones.select { |z| zone_names.include?(z.name) }.sort_by { |z| zone_names.index(z.name) }.map { |z| [ z.to_s, z.name ] }
end

def workshop_selected_value(story_idea, params)
# If editing and has external title but no workshop, don't select anything
return nil if story_idea.workshop_id.nil? && story_idea.external_workshop_title.present?

# Otherwise use param or object value
params[:workshop_id].presence || story_idea.workshop_id
end

def show_external_workshop_field?(story_idea)
story_idea.workshop_id.nil? && story_idea.external_workshop_title.present?
end
end

Check failure on line 185 in app/helpers/application_helper.rb

View workflow job for this annotation

GitHub Actions / rubocop

Layout/TrailingEmptyLines: 1 trailing blank lines detected.
10 changes: 10 additions & 0 deletions app/models/story_idea.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
validates :body, presence: true
validates :permission_given, presence: true
validates :publish_preferences, presence: true
validate :workshop_or_external_title_present

# Nested attributes
accepts_nested_attributes_for :primary_asset, allow_destroy: true, reject_if: :all_blank
Expand Down Expand Up @@ -83,4 +84,13 @@
def organization_description
organization&.organization_description
end

private

def workshop_or_external_title_present
if workshop_id.blank? && external_workshop_title.blank?
errors.add(:base, "Please select a workshop or enter an external workshop title")
end
end
end

Check failure on line 96 in app/models/story_idea.rb

View workflow job for this annotation

GitHub Actions / rubocop

Layout/TrailingEmptyLines: 1 trailing blank lines detected.
69 changes: 47 additions & 22 deletions app/views/story_ideas/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,53 @@
class: ("readonly" if promoted_to_story)
} %>
</div>
<div class="flex-1 mb-4 md:mb-0">
<%= f.input :workshop_id,
as: :select,
collection: @workshops,
label_method: :type_name,
value_method: :id,
label: "Workshop",
prompt: "Select a Workshop",
required: true,
disabled: promoted_to_story,
selected: params[:workshop_id].presence || f.object.workshop_id,
input_html: { value: params[:workshop_id].presence || f.object.workshop_id,
class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500
focus:border-blue-500 #{ 'readonly' if promoted_to_story }" },
label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
<%= f.input :external_workshop_title,
as: :text,
label: "External title (if no workshop above)",
input_html: {
rows: 1,
class: ("readonly" if promoted_to_story)
} %>
<div class="flex-1 mb-4 md:mb-0" data-controller="workshop-toggle">
<div data-workshop-toggle-target="dropdown">
<%= f.input :workshop_id,
as: :select,
collection: @workshops,
label_method: :type_name,
value_method: :id,
label: "Workshop",
prompt: "Select a Workshop",
required: false,
disabled: promoted_to_story,
selected: workshop_selected_value(f.object, params),
input_html: { value: params[:workshop_id].presence || f.object.workshop_id,
class: "block w-full rounded-md border-gray-300 shadow-sm focus:ring-blue-500
focus:border-blue-500 #{ 'readonly' if promoted_to_story }",
data: { action: "change->workshop-toggle#handleChange" } },
label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
</div>
<div data-workshop-toggle-target="externalField" class="<%= 'hidden' unless show_external_workshop_field?(f.object) %>">
<div class="flex items-start gap-2">
<div class="flex-1">
<%= f.input :external_workshop_title,
as: :text,
label: "Workshop Title",
wrapper: false,
input_html: {
rows: 1,
class: ("readonly" if promoted_to_story)
},
label_html: { class: "block text-sm font-medium text-gray-700 mb-1" } %>
</div>
<% unless promoted_to_story %>
<button type="button"
class="mt-7 p-2 text-gray-400 hover:text-gray-600"
data-action="click->workshop-toggle#showDropdown"
title="Back to dropdown">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<% end %>
</div>
</div>
</div>
<% end %>
</div>
</div>
</div>
<div class="flex-1 mb-4 md:mb-0">
<% if current_user.person&.affiliations&.count != 1 %>
Expand Down
52 changes: 52 additions & 0 deletions spec/models/story_idea_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require "rails_helper"

RSpec.describe StoryIdea, type: :model do
describe "validations" do
context "workshop selection" do
let(:valid_story_idea) { create(:story_idea) }

it "is valid with a workshop_id" do
story_idea = create(:story_idea, external_workshop_title: nil)
expect(story_idea).to be_persisted
expect(story_idea.workshop).to be_present
end

it "is valid with external_workshop_title and no workshop" do
story_idea = create(:story_idea,
workshop: nil,
external_workshop_title: "My External Workshop")
expect(story_idea).to be_persisted
expect(story_idea.external_workshop_title).to eq("My External Workshop")
end

it "is invalid without workshop_id or external_workshop_title" do
story_idea = build(:story_idea,
workshop: nil,
external_workshop_title: nil)
expect(story_idea).not_to be_valid
expect(story_idea.errors[:base]).to include("Please select a workshop or enter an external workshop title")
end

it "is valid with both workshop_id and external_workshop_title" do
story_idea = create(:story_idea,
external_workshop_title: "My External Workshop")
expect(story_idea).to be_persisted
expect(story_idea.workshop).to be_present
expect(story_idea.external_workshop_title).to eq("My External Workshop")
end
end
end

describe "#workshop_title" do
it "returns workshop title when workshop is set" do
workshop = create(:workshop, title: "Art Therapy 101")
story_idea = create(:story_idea, workshop: workshop, external_workshop_title: nil)
expect(story_idea.workshop_title).to eq("Art Therapy 101")
end

it "returns external title in brackets when no workshop" do
story_idea = create(:story_idea, workshop: nil, external_workshop_title: "Community Workshop")
expect(story_idea.workshop_title).to eq("[Community Workshop]")
end
end
end
13 changes: 13 additions & 0 deletions spec/requests/story_ideas_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@
}.to change(StoryIdea, :count).by(1)
end

it "creates a StoryIdea with external_workshop_title when workshop_id is 'new'" do
attrs = valid_attributes.merge(
workshop_id: "new",
external_workshop_title: "My Custom Workshop"
)

post story_ideas_url, params: { story_idea: attrs }

story_idea = StoryIdea.last
expect(story_idea.workshop_id).to be_nil
expect(story_idea.external_workshop_title).to eq("My Custom Workshop")
end

it "redirects to root after create" do
post story_ideas_url, params: { story_idea: valid_attributes }
expect(response).to redirect_to(root_path)
Expand Down
Loading