diff --git a/Gemfile b/Gemfile
index 189d3a5db..1e6a5d305 100644
--- a/Gemfile
+++ b/Gemfile
@@ -46,6 +46,7 @@ gem "bootsnap", require: false
# gem 'image_processing', '~> 1.2'
gem "flipflop"
+gem "redcarpet", "~> 3.6"
gem "govuk-components", "~> 5.0.0"
gem "govuk_design_system_formbuilder", "~> 5.0.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index 2c7b83626..1c1e92c5a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -79,7 +79,7 @@ GEM
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
- addressable (2.8.5)
+ addressable (2.8.6)
public_suffix (>= 2.0.2, < 6.0)
aes_key_wrap (1.1.0)
annotate (3.2.0)
@@ -176,7 +176,7 @@ GEM
activemodel (>= 6.1)
activesupport (>= 6.1)
html-attributes-utils (~> 1)
- haml (6.2.3)
+ haml (6.3.0)
temple (>= 0.8.2)
thor
tilt
@@ -185,7 +185,7 @@ GEM
activesupport (>= 6.1.4.4)
i18n (1.14.1)
concurrent-ruby (~> 1.0)
- io-console (0.7.0)
+ io-console (0.6.0)
irb (1.10.1)
rdoc
reline (>= 0.3.8)
@@ -227,7 +227,7 @@ GEM
minitest (5.20.0)
msgpack (1.7.2)
mutex_m (0.2.0)
- net-imap (0.4.7)
+ net-imap (0.4.8)
date
net-protocol
net-pop (0.1.2)
@@ -237,7 +237,7 @@ GEM
net-smtp (0.4.0)
net-protocol
nio4r (2.7.0)
- nokogiri (1.15.5-arm64-darwin)
+ nokogiri (1.15.5-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.15.5-x86_64-linux)
racc (~> 1.4)
@@ -292,7 +292,7 @@ GEM
puma (6.4.0)
nio4r (~> 2.0)
racc (1.7.3)
- rack (2.2.8)
+ rack (3.0.8)
rack-oauth2 (2.2.0)
activesupport
attr_required
@@ -302,13 +302,13 @@ GEM
rack (>= 2.1.0)
rack-protection (3.0.6)
rack
- rack-session (1.0.2)
- rack (< 3)
+ rack-session (2.0.0)
+ rack (>= 3.0.0)
rack-test (2.1.0)
rack (>= 1.3)
- rackup (1.0.0)
- rack (< 3)
- webrick
+ rackup (2.1.0)
+ rack (>= 3)
+ webrick (~> 1.8)
rails (7.1.2)
actioncable (= 7.1.2)
actionmailbox (= 7.1.2)
@@ -352,6 +352,7 @@ GEM
rbs (2.8.4)
rdoc (6.6.1)
psych (>= 4.0.0)
+ redcarpet (3.6.0)
regexp_parser (2.8.3)
reline (0.4.1)
io-console (~> 0.5)
@@ -503,6 +504,7 @@ GEM
PLATFORMS
arm64-darwin-22
arm64-darwin-23
+ x86_64-darwin-22
x86_64-linux
DEPENDENCIES
@@ -536,6 +538,7 @@ DEPENDENCIES
rails (~> 7.1.2)
rails-controller-testing
rails-erd
+ redcarpet (~> 3.6)
rladr
rspec
rspec-rails
diff --git a/app/assets/components/service_update/view.html.erb b/app/assets/components/service_update/view.html.erb
new file mode 100644
index 000000000..295df2014
--- /dev/null
+++ b/app/assets/components/service_update/view.html.erb
@@ -0,0 +1,5 @@
+
+ <%= title_element %>
+ <%= date_pretty %>
+ <%= content_html %>
+
diff --git a/app/assets/components/service_update/view.rb b/app/assets/components/service_update/view.rb
new file mode 100644
index 000000000..6b67798f7
--- /dev/null
+++ b/app/assets/components/service_update/view.rb
@@ -0,0 +1,26 @@
+class ServiceUpdate::View < GovukComponent::Base
+ attr_reader :service_update
+
+ delegate :title, :content, :date, to: :service_update
+
+ TITLE_CLASS = "govuk-heading-m govuk-!-margin-bottom-2"
+
+ def initialize(service_update:)
+ @service_update = service_update
+ end
+
+ def title_element
+ tag.h2(title, class: TITLE_CLASS)
+ end
+
+ def date_pretty
+ date.to_date.strftime("%e %B %Y")
+ end
+
+ def content_html
+ custom_render =
+ Redcarpet::Render::HTML.new(link_attributes: { class: "govuk-link" })
+ markdown = Redcarpet::Markdown.new(custom_render)
+ markdown.render(content).html_safe
+ end
+end
diff --git a/app/controllers/service_updates_controller.rb b/app/controllers/service_updates_controller.rb
new file mode 100644
index 000000000..8b3acb40a
--- /dev/null
+++ b/app/controllers/service_updates_controller.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class ServiceUpdatesController < ApplicationController
+ include ApplicationHelper
+
+ def index
+ @service_name ||= current_service
+ @service_updates = ServiceUpdate.where(service: @service_name)
+ end
+end
diff --git a/app/models/service_update.rb b/app/models/service_update.rb
new file mode 100644
index 000000000..33470d77a
--- /dev/null
+++ b/app/models/service_update.rb
@@ -0,0 +1,34 @@
+class ServiceUpdate
+ CLAIMS_SERVICE_UPDATES_YAML_FILE =
+ Rails.root.join("db/claims_service_updates.yml").freeze
+ PLACEMENTS_SERVICE_UPDATES_YAML_FILE =
+ Rails.root.join("db/placements_service_updates.yml").freeze
+
+ attr_accessor :date, :title, :content
+
+ def initialize(title:, date:, content:)
+ @title = title
+ @date = date
+ @content = content
+ end
+
+ def id
+ title.parameterize
+ end
+
+ def self.where(service:)
+ path = file_path(service:)
+ updates = YAML.load_file(path, symbolize_names: true) || []
+
+ updates.map { |service_update| new(**service_update) }
+ end
+
+ def self.file_path(service:)
+ case service
+ when :claims
+ CLAIMS_SERVICE_UPDATES_YAML_FILE
+ when :placements
+ PLACEMENTS_SERVICE_UPDATES_YAML_FILE
+ end
+ end
+end
diff --git a/app/views/service_updates/index.html.erb b/app/views/service_updates/index.html.erb
new file mode 100644
index 000000000..fb52b41f9
--- /dev/null
+++ b/app/views/service_updates/index.html.erb
@@ -0,0 +1,17 @@
+
+
+
<%= t("service_updates.#{@service_name}.heading") %>
+
+
<%= t("service_updates.contents") %>
+
+
+ <% @service_updates.each do |service_update| %>
+
+ <%= render ServiceUpdate::View.new(service_update:) %>
+ <% end %>
+
+
diff --git a/config/application.rb b/config/application.rb
index 43e34318d..fdcd444d3 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -35,6 +35,7 @@ class Application < Rails::Application
"node_modules/govuk-frontend/dist/govuk/assets"
)
+ config.autoload_paths += %W[#{config.root}/app/assets/components]
config.exceptions_app = routes
end
end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e3b586c4e..e64e4f518 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -38,3 +38,9 @@ en:
roles_include: "%{persona_name}’s role includes:"
sign_in_as: "Sign In as %{persona_name}"
title: Test users
+ service_updates:
+ claims:
+ heading: Claims news and updates
+ placements:
+ heading: Placements news and updates
+ contents: Contents
diff --git a/config/routes.rb b/config/routes.rb
index 66a14a6d4..bcfdf0e9e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -17,6 +17,8 @@
post("/auth/developer/callback", to: "sessions#callback")
end
+ resources :service_updates, only: %i[index]
+
draw :placements
draw :claims
diff --git a/db/claims_service_updates.yml b/db/claims_service_updates.yml
new file mode 100644
index 000000000..79867c3f9
--- /dev/null
+++ b/db/claims_service_updates.yml
@@ -0,0 +1,11 @@
+# Example structure for adding service updates
+
+# - date:
+# type: String
+# format: YYYY-MM-DD
+# title:
+# type: String
+# format: Short text
+# content:
+# type: String
+# format: Multi-line text
diff --git a/db/placements_service_updates.yml b/db/placements_service_updates.yml
new file mode 100644
index 000000000..79867c3f9
--- /dev/null
+++ b/db/placements_service_updates.yml
@@ -0,0 +1,11 @@
+# Example structure for adding service updates
+
+# - date:
+# type: String
+# format: YYYY-MM-DD
+# title:
+# type: String
+# format: Short text
+# content:
+# type: String
+# format: Multi-line text
diff --git a/spec/models/service_update_spec.rb b/spec/models/service_update_spec.rb
new file mode 100644
index 000000000..d90c94c54
--- /dev/null
+++ b/spec/models/service_update_spec.rb
@@ -0,0 +1,72 @@
+# spec/models/service_update_spec.rb
+
+require "rails_helper"
+
+RSpec.describe ServiceUpdate do
+ describe ".where" do
+ context "when service is :claims" do
+ it "returns updates for claims" do
+ allow(YAML).to receive(:load_file).and_return(
+ [
+ {
+ date: "2023-12-14",
+ title: "Claim Update",
+ content: "Some content"
+ }
+ ]
+ )
+ updates = ServiceUpdate.where(service: :claims)
+ expect(updates.length).to eq(1)
+ expect(updates.first.title).to eq("Claim Update")
+ end
+
+ it "returns an empty hash when no updates for claims" do
+ allow(YAML).to receive(:load_file).and_return(nil)
+ updates = ServiceUpdate.where(service: :claims)
+ expect(updates).to eq([])
+ end
+ end
+
+ context "when service is :placements" do
+ it "returns updates for placements" do
+ allow(YAML).to receive(:load_file).and_return(
+ [
+ {
+ date: "2023-12-14",
+ title: "Placement Update",
+ content: "Some content"
+ }
+ ]
+ )
+ updates = ServiceUpdate.where(service: :placements)
+ expect(updates.length).to eq(1)
+ expect(updates.first.title).to eq("Placement Update")
+ end
+
+ it "returns an empty hash when no updates for placements" do
+ allow(YAML).to receive(:load_file).and_return(nil)
+ updates = ServiceUpdate.where(service: :placements)
+ expect(updates).to eq([])
+ end
+ end
+ end
+
+ describe ".yaml_file" do
+ it "returns claims YAML file path" do
+ file_path = ServiceUpdate.file_path(service: :claims)
+ expect(file_path).to eq(Rails.root.join("db/claims_service_updates.yml"))
+ end
+
+ it "returns placements YAML file path" do
+ file_path = ServiceUpdate.file_path(service: :placements)
+ expect(file_path).to eq(
+ Rails.root.join("db/placements_service_updates.yml")
+ )
+ end
+
+ it "returns nil for unknown service" do
+ file_path = ServiceUpdate.file_path(service: :some_other_random_service)
+ expect(file_path).to be_nil
+ end
+ end
+end
diff --git a/spec/system/service_updates_page_spec.rb b/spec/system/service_updates_page_spec.rb
new file mode 100644
index 000000000..c9f553b67
--- /dev/null
+++ b/spec/system/service_updates_page_spec.rb
@@ -0,0 +1,43 @@
+require "rails_helper"
+
+RSpec.feature "Service updates page" do
+ after { Capybara.app_host = nil }
+
+ scenario "User visits the claims Service updates" do
+ given_i_am_on_the_claims_site
+ and_i_am_on_the_service_updates_page
+ i_can_see_the_claims_service_title
+ end
+
+ scenario "User visits the placements Service updates" do
+ given_i_am_on_the_placements_site
+ and_i_am_on_the_service_updates_page
+ i_can_see_the_placements_service_title
+ end
+
+ private
+
+ def given_i_am_on_the_claims_site
+ Capybara.app_host = "http://#{ENV["CLAIMS_HOST"]}"
+ end
+
+ def given_i_am_on_the_placements_site
+ Capybara.app_host = "http://#{ENV["PLACEMENTS_HOST"]}"
+ end
+
+ def and_i_am_on_the_service_updates_page
+ visit "/service_updates"
+ end
+
+ def i_can_see_the_claims_service_title
+ within(".govuk-heading-xl") do
+ expect(page).to have_content("Claims news and updates")
+ end
+ end
+
+ def i_can_see_the_placements_service_title
+ within(".govuk-heading-xl") do
+ expect(page).to have_content("Placements news and updates")
+ end
+ end
+end