diff --git a/app/javascript/controllers/better_together/uploads_controller.js b/app/javascript/controllers/better_together/uploads_controller.js
new file mode 100644
index 000000000..8be2b247e
--- /dev/null
+++ b/app/javascript/controllers/better_together/uploads_controller.js
@@ -0,0 +1,37 @@
+import { Controller } from "@hotwired/stimulus"
+
+export default class extends Controller {
+ static targets = ["search", "sort", "list", "item"]
+
+ filter() {
+ const query = this.searchTarget.value.toLowerCase()
+ this.itemTargets.forEach((item) => {
+ const name = item.dataset.name.toLowerCase()
+ item.classList.toggle("d-none", !name.includes(query))
+ })
+ }
+
+ sort() {
+ const key = this.sortTarget.value
+ const items = this.itemTargets.slice().sort((a, b) => {
+ if (key === "name") {
+ return a.dataset.name.localeCompare(b.dataset.name)
+ }
+ if (key === "created_at") {
+ return new Date(b.dataset.createdAt) - new Date(a.dataset.createdAt)
+ }
+ return 0
+ })
+ items.forEach((item) => this.listTarget.appendChild(item))
+ }
+
+ copy(event) {
+ const url = event.currentTarget.dataset.url
+ navigator.clipboard?.writeText(url)
+ }
+
+ insert(event) {
+ const signedId = event.currentTarget.dataset.signedId
+ this.dispatch("insert", { detail: { signedId } })
+ }
+}
diff --git a/app/views/better_together/uploads/index.html.erb b/app/views/better_together/uploads/index.html.erb
index b10c9114b..19a5cc416 100644
--- a/app/views/better_together/uploads/index.html.erb
+++ b/app/views/better_together/uploads/index.html.erb
@@ -1,2 +1,41 @@
-
Files#index
-Find me in app/views/better_together/files/index.html.erb
+<% content_for :page_title do %>
+ <%= BetterTogether::Upload.model_name.human.pluralize %>
+<% end %>
+
+
+
+
+
+
+
+
+
+
+
+
+ <% @uploads.each do |upload| %>
+
+
+ <% if upload.file.attached? %>
+ <%= image_tag upload.file.variant(resize_to_limit: [200, 200]), class: "card-img-top", alt: upload.name %>
+ <% end %>
+
+
<%= upload.name %>
+
+ - Filename: <%= upload.filename %>
+ - Size: <%= number_to_human_size(upload.byte_size) %>
+ - Type: <%= upload.content_type %>
+
+
+
+
+
+
+
+
+ <% end %>
+
+
diff --git a/spec/features/uploads/gallery_spec.rb b/spec/features/uploads/gallery_spec.rb
new file mode 100644
index 000000000..94ed44a38
--- /dev/null
+++ b/spec/features/uploads/gallery_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe 'uploads gallery', type: :feature, js: true do # rubocop:disable Metrics/BlockLength
+ include BetterTogether::DeviseSessionHelpers
+
+ before do
+ configure_host_platform
+ login_as_platform_manager
+ @creator = BetterTogether::User.find_by(email: 'manager@example.test').person
+ end
+
+ def create_upload(name, creator:, created_at: Time.current)
+ create(:better_together_upload, name:, creator:, created_at:).tap do |upload|
+ upload.file.attach(io: StringIO.new('stub'), filename: "#{name}.txt", content_type: 'text/plain')
+ end
+ end
+
+ scenario 'searches, sorts, and copies upload urls' do
+ create_upload('Alpha', creator: @creator, created_at: 2.days.ago)
+ create_upload('Beta', creator: @creator, created_at: 1.day.ago)
+
+ visit file_index_path(locale: I18n.default_locale)
+
+ expect(page.all('[data-better_together--uploads-target="item"] .card-title').map(&:text))
+ .to eq(%w[Beta Alpha])
+
+ fill_in placeholder: 'Search uploads', with: 'Al'
+ expect(page).to have_selector('[data-name="Alpha"]', visible: true)
+ expect(page).to have_selector('[data-name="Beta"].d-none', visible: :all)
+
+ find('select[data-better_together--uploads-target="sort"]').select('Name')
+ expect(page.all('[data-better_together--uploads-target="item"] .card-title').map(&:text))
+ .to eq(%w[Alpha Beta])
+
+ copy_button = find('button', text: 'Copy URL', match: :first)
+ page.execute_script <<~JS
+ window.copiedText = null;
+ Object.defineProperty(navigator, "clipboard", {
+ value: { writeText: function(t){ window.copiedText = t } }
+ });
+ JS
+ copy_button.click
+ expect(page.evaluate_script('window.copiedText')).to eq(copy_button['data-url'])
+ end
+end
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 3cf31fd4e..79facd8ef 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -20,11 +20,14 @@
)
# Generate a unique temporary directory for each session to avoid conflicts
options.add_argument("--user-data-dir=#{Dir.mktmpdir}")
+ options.binary = '/usr/bin/chromium-browser'
+ service = Selenium::WebDriver::Service.chrome(path: '/usr/bin/chromedriver')
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
- options: options
+ options: options,
+ service: service
)
end