diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm
index 845e926bf4d..231288cebca 100644
--- a/perllib/FixMyStreet/App/Controller/Waste.pm
+++ b/perllib/FixMyStreet/App/Controller/Waste.pm
@@ -742,7 +742,13 @@ sub bin_day_deny : Private {
$c->detach('/page_error_403_access_denied', [ $msg ]);
}
-sub calendar : Chained('property') : PathPart('calendar.ics') : Args(0) {
+sub calendar : Chained('property') : Args(0) {
+ my ($self, $c) = @_;
+
+ $c->forward('/about/page', ['waste-calendar']);
+}
+
+sub calendar_ics : Chained('property') : PathPart('calendar.ics') : Args(0) {
my ($self, $c) = @_;
$c->res->header(Content_Type => 'text/calendar');
require Data::ICal::RFC7986;
diff --git a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm
index 4ff0d706dbd..ee78556bbbd 100644
--- a/perllib/FixMyStreet/Roles/Cobrand/Echo.pm
+++ b/perllib/FixMyStreet/Roles/Cobrand/Echo.pm
@@ -90,7 +90,7 @@ sub bin_addresses_for_postcode {
sub _allow_async_echo_lookup {
my $self = shift;
my $action = $self->{c}->action;
- return 0 if $action eq 'waste/pay_retry' || $action eq 'waste/direct_debit_error' || $action eq 'waste/calendar';
+ return 0 if $action eq 'waste/pay_retry' || $action eq 'waste/direct_debit_error' || $action eq 'waste/calendar_ics';
return 1;
}
diff --git a/t/app/controller/waste.t b/t/app/controller/waste.t
index c55360fa771..4c51c5dbe07 100644
--- a/t/app/controller/waste.t
+++ b/t/app/controller/waste.t
@@ -333,7 +333,8 @@ FixMyStreet::override_config {
is $mech->uri->path, '/waste/12345';
};
subtest 'Checking calendar' => sub {
- $mech->follow_link_ok({ text => 'Add to your calendar (.ics file)' });
+ $mech->follow_link_ok({ text => 'Add to your calendar' });
+ $mech->follow_link_ok({ text_regex => qr/this link/ });
$mech->content_contains('BEGIN:VCALENDAR');
my @events = split /BEGIN:VEVENT/, $mech->encoded_content;
shift @events; # Header
diff --git a/t/app/controller/waste_bexley.t b/t/app/controller/waste_bexley.t
index e236c16e301..7360d97b966 100644
--- a/t/app/controller/waste_bexley.t
+++ b/t/app/controller/waste_bexley.t
@@ -456,8 +456,8 @@ FixMyStreet::override_config {
}
subtest 'Checking calendar' => sub {
- $mech->follow_link_ok(
- { text => 'Add to your calendar (.ics file)' } );
+ $mech->follow_link_ok({ text => 'Add to your calendar' });
+ $mech->follow_link_ok({ text_regex => qr/this link/ });
$mech->content_contains('BEGIN:VCALENDAR');
my @events = split /BEGIN:VEVENT/, $mech->encoded_content;
shift @events; # Header
diff --git a/templates/web/base/waste/bin_days.html b/templates/web/base/waste/bin_days.html
index 93d820853ef..37d62bb8cbd 100644
--- a/templates/web/base/waste/bin_days.html
+++ b/templates/web/base/waste/bin_days.html
@@ -20,7 +20,7 @@
Your collections
Your collections
[% IF service_data.size %]
[% TRY %][% PROCESS waste/_calendar.html %][% CATCH file %]
- Add to your calendar (.ics file)
+ Add to your calendar
[% END %]
[% END %]
diff --git a/templates/web/base/waste/bin_days_sidebar.html b/templates/web/base/waste/bin_days_sidebar.html
index 4f1ebf27b3a..1ea4221ebf6 100644
--- a/templates/web/base/waste/bin_days_sidebar.html
+++ b/templates/web/base/waste/bin_days_sidebar.html
@@ -2,7 +2,7 @@
Download your collection schedule
- - Add to your calendar (.ics file)
+ - Add to your calendar
[% IF c.cobrand.moniker == 'kingston' %]
- Download PDF waste calendar
[% ELSIF c.cobrand.moniker == 'sutton' %]
diff --git a/templates/web/base/waste/header.html b/templates/web/base/waste/header.html
index d38cafef7f5..d7cf62d482a 100644
--- a/templates/web/base/waste/header.html
+++ b/templates/web/base/waste/header.html
@@ -5,6 +5,7 @@
END;
%]
[% extra_js = [
+ [ version('/vendor/clipboard-copy-element.js'), { type = "module" } ],
version('/vendor/accessible-autocomplete.min.js'),
version('/js/waste.js')
] -%]
diff --git a/templates/web/borsetshire/waste/bin_days_sidebar.html b/templates/web/borsetshire/waste/bin_days_sidebar.html
index 9bb9ec348d7..f1863303e7b 100644
--- a/templates/web/borsetshire/waste/bin_days_sidebar.html
+++ b/templates/web/borsetshire/waste/bin_days_sidebar.html
@@ -2,7 +2,7 @@
Free bin day reminders
diff --git a/templates/web/fixmystreet-uk-councils/about/waste-calendar.html b/templates/web/fixmystreet-uk-councils/about/waste-calendar.html
new file mode 100644
index 00000000000..15a0aa9cb4c
--- /dev/null
+++ b/templates/web/fixmystreet-uk-councils/about/waste-calendar.html
@@ -0,0 +1,62 @@
+[%
+PROCESS 'waste/header.html', title = 'Add your waste collection days to your online calendar';
+SET ics_link = c.uri_for_action('waste/calendar_ics', [ property.id ]);
+%]
+
+
+
+
+
+
+
+
Add your waste collection days to your online calendar
+
+
Subscribe using your smartphone or computer, and your online calendar will always show your next collection days.
+
+
Step 1: copy the link to the calendar
+
+
+
Click the Copy button below to copy the calendar link to your clipboard:
+
+ Loading...
+ Copy
+
+
+
+
Or press and hold this link until a menu appears, then tap “Copy Link” or “Copy Link Address”.
+
Or right-click / Ctrl-click this link, then click “Copy Link” or “Copy Link Address”.
+
+
+
+
Step 2: subscribe to the calendar
+
Follow these instructions and paste in the link you just copied when it asks you for the calendar’s address or URL.
+
+
+
+
+[% INCLUDE footer.html %]
diff --git a/templates/web/peterborough/waste/_calendar.html b/templates/web/peterborough/waste/_calendar.html
index 2c4b192a6c9..6a6d1763005 100644
--- a/templates/web/peterborough/waste/_calendar.html
+++ b/templates/web/peterborough/waste/_calendar.html
@@ -1 +1 @@
-Add to your calendar (.ics file)
+Add to your calendar
diff --git a/web/vendor/clipboard-copy-element.js b/web/vendor/clipboard-copy-element.js
new file mode 100644
index 00000000000..1b46e3e68f8
--- /dev/null
+++ b/web/vendor/clipboard-copy-element.js
@@ -0,0 +1,8 @@
+/**
+ * Bundled by jsDelivr using Rollup v2.79.1 and Terser v5.19.2.
+ * Original file: /npm/@github/clipboard-copy-element@1.3.0/dist/index.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+function e(e){if("clipboard"in navigator)return navigator.clipboard.writeText(e.textContent||"");const t=getSelection();if(null==t)return Promise.reject(new Error);t.removeAllRanges();const n=document.createRange();return n.selectNodeContents(e),t.addRange(n),document.execCommand("copy"),t.removeAllRanges(),Promise.resolve()}function t(t){if("clipboard"in navigator)return navigator.clipboard.writeText(t);const n=document.body;if(!n)return Promise.reject(new Error);const o=function(e){const t=document.createElement("pre");return t.style.width="1px",t.style.height="1px",t.style.position="fixed",t.style.top="5px",t.textContent=e,t}(t);return n.appendChild(o),e(o),n.removeChild(o),Promise.resolve()}async function n(n){const o=n.getAttribute("for"),r=n.getAttribute("value");function i(){n.dispatchEvent(new CustomEvent("clipboard-copy",{bubbles:!0}))}var a;if("true"!==n.getAttribute("aria-disabled"))if(r)await t(r),i();else if(o){const r="getRootNode"in Element.prototype?n.getRootNode():n.ownerDocument;if(!(r instanceof Document||"ShadowRoot"in window&&r instanceof ShadowRoot))return;const s=r.getElementById(o);s&&(await(a=s,a instanceof HTMLInputElement||a instanceof HTMLTextAreaElement?t(a.value):a instanceof HTMLAnchorElement&&a.hasAttribute("href")?t(a.href):e(a)),i())}}function o(e){const t=e.currentTarget;t instanceof HTMLElement&&n(t)}function r(e){if(" "===e.key||"Enter"===e.key){const t=e.currentTarget;t instanceof HTMLElement&&(e.preventDefault(),n(t))}}function i(e){e.currentTarget.addEventListener("keydown",r)}function a(e){e.currentTarget.removeEventListener("keydown",r)}class s extends HTMLElement{static define(e="clipboard-copy",t=customElements){return t.define(e,this),this}constructor(){super(),this.addEventListener("click",o),this.addEventListener("focus",i),this.addEventListener("blur",a)}connectedCallback(){this.hasAttribute("tabindex")||this.setAttribute("tabindex","0"),this.hasAttribute("role")||this.setAttribute("role","button")}get value(){return this.getAttribute("value")||""}set value(e){this.setAttribute("value",e)}}const c="undefined"!=typeof globalThis?globalThis:window;try{c.ClipboardCopyElement=s.define()}catch(e){if(!(c.DOMException&&e instanceof DOMException&&"NotSupportedError"===e.name||e instanceof ReferenceError))throw e}export{s as ClipboardCopyElement,s as default};
+//# sourceMappingURL=/sm/d07c5d884b8a4edb8adc6ad670c97cd087b3bad123400edf84c6aab5193dff65.map
\ No newline at end of file