feat(events): added "Add to Google Calendar" button; and clarify .ics download
This commit is contained in:
@@ -67,7 +67,8 @@
|
||||
"applyToJoin": "قدّم طلب الانضمام",
|
||||
"viewUpcomingSessions": "اطّلع على الجلسات القادمة",
|
||||
"submitYourUpdate": "شاركونا مستجداتكم",
|
||||
"addToCalendar": "أضف إلى التقويم",
|
||||
"addToGoogleCalendar": "أضف إلى تقويم Google",
|
||||
"downloadIcsFile": "تنزيل ملف .ics",
|
||||
"tryAgain": "حاول مرة أخرى"
|
||||
},
|
||||
"filters": {
|
||||
|
||||
@@ -67,7 +67,8 @@
|
||||
"applyToJoin": "Apply to join",
|
||||
"viewUpcomingSessions": "View upcoming sessions",
|
||||
"submitYourUpdate": "Submit your update",
|
||||
"addToCalendar": "Add to calendar",
|
||||
"addToGoogleCalendar": "Add to Google Calendar",
|
||||
"downloadIcsFile": "Download .ics file",
|
||||
"tryAgain": "Try again"
|
||||
},
|
||||
"filters": {
|
||||
|
||||
@@ -65,7 +65,8 @@ const EN = {
|
||||
eventCompleted: "This event has already taken place.",
|
||||
joinOnlineLabel: "Joining link",
|
||||
addToCalendarHint:
|
||||
"Downloads an .ics file you can open in any calendar application.",
|
||||
"The .ics file is compatible with Calendar on Apple devices and " +
|
||||
"other calendar apps.",
|
||||
timeZoneNote: "All times are shown in Libya time (UTC+2).",
|
||||
/**
|
||||
* `event-registration.consentGdpr` is REQUIRED in the CMS, so the reader
|
||||
@@ -259,7 +260,9 @@ const AR: RouteCopy = {
|
||||
"اكتمل العدد في هذه الفعالية. انضم إلى قائمة الانتظار وسنتواصل معك في حال توفّر مكان.",
|
||||
eventCompleted: "انتهت هذه الفعالية.",
|
||||
joinOnlineLabel: "رابط الانضمام",
|
||||
addToCalendarHint: "يُنزّل ملف .ics يمكن فتحه في أي تطبيق تقويم.",
|
||||
addToCalendarHint:
|
||||
"ملف .ics متوافق مع تطبيق التقويم على أجهزة Apple ومع تطبيقات " +
|
||||
"التقويم الأخرى.",
|
||||
timeZoneNote: "جميع الأوقات معروضة بتوقيت ليبيا (UTC+2).",
|
||||
consentLabel:
|
||||
"أوافق على أن يحتفظ مشروع SHARP-ER بالبيانات أعلاه لإدارة تسجيلي في هذه الفعالية.",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/* ============================================================================
|
||||
iCALENDAR (RFC 5545)
|
||||
----------------------------------------------------------------------------
|
||||
Builds the file behind "Add to calendar". Pure, dependency-free and written
|
||||
Builds the file behind "Download .ics file". Pure, dependency-free and written
|
||||
against the spec rather than against one client's tolerance:
|
||||
|
||||
* CRLF line endings, and content lines folded at 75 OCTETS - folded on
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
refuses to submit without it. It is never a hidden input: consent that the
|
||||
reader did not give is not consent.
|
||||
|
||||
"Add to calendar" points at the sibling resource route,
|
||||
/{locale}/events-calendar/{slug}.ics, which streams a real RFC 5545 file.
|
||||
The .ics download points at the sibling resource route,
|
||||
/{locale}/events-calendar/{slug}.ics, which streams a real RFC 5545 file.
|
||||
|
||||
ROUTE
|
||||
Registered in app/routes.ts as "eventsDetail", a SIBLING of the listing route
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
formatEventWhen,
|
||||
isRegistrationOpen,
|
||||
isWaitlisted,
|
||||
type EventDetailData,
|
||||
} from "./_events-opportunities/events";
|
||||
import { fetchEvent } from "./_events-opportunities/events.server";
|
||||
import {
|
||||
@@ -98,6 +99,47 @@ import { RichText } from "./_shared/RichText";
|
||||
*/
|
||||
export const handle = SELF_ALTERNATES_HANDLE;
|
||||
|
||||
function addCalendarDay(date: Date): Date {
|
||||
const next = new Date(date);
|
||||
next.setUTCDate(next.getUTCDate() + 1);
|
||||
return next;
|
||||
}
|
||||
|
||||
function toGoogleCalendarDate(date: Date): string {
|
||||
return date.toISOString().slice(0, 10).replace(/-/g, "");
|
||||
}
|
||||
|
||||
function toGoogleCalendarDateTime(date: Date): string {
|
||||
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
|
||||
}
|
||||
|
||||
function buildGoogleCalendarHref(event: EventDetailData): string {
|
||||
const start = new Date(event.start);
|
||||
const parsedEnd = event.end ? new Date(event.end) : undefined;
|
||||
const end =
|
||||
parsedEnd && !Number.isNaN(parsedEnd.getTime()) ? parsedEnd : undefined;
|
||||
const dates = event.allDay
|
||||
? `${toGoogleCalendarDate(start)}/${toGoogleCalendarDate(
|
||||
addCalendarDay(end ?? start),
|
||||
)}`
|
||||
: `${toGoogleCalendarDateTime(start)}/${toGoogleCalendarDateTime(
|
||||
end && end.getTime() > start.getTime() ? end : start,
|
||||
)}`;
|
||||
|
||||
const params = new URLSearchParams({
|
||||
action: "TEMPLATE",
|
||||
text: event.title,
|
||||
dates,
|
||||
});
|
||||
const details = [event.summary, event.onlineUrl].filter(Boolean).join("\n\n");
|
||||
const location = event.place ?? event.venue;
|
||||
|
||||
if (details) params.set("details", details);
|
||||
if (location) params.set("location", location);
|
||||
|
||||
return `https://calendar.google.com/calendar/render?${params.toString()}`;
|
||||
}
|
||||
|
||||
export async function loader({ params }: Route.LoaderArgs) {
|
||||
const locale = requireLocale(params.lang);
|
||||
const slug = params.slug ?? "";
|
||||
@@ -429,6 +471,24 @@ export default function EventDetail() {
|
||||
<DetailList heading={copy.eventDetail.detailsHeading} items={details} />
|
||||
|
||||
<div className="flex flex-col gap-xs">
|
||||
<Button
|
||||
as="a"
|
||||
href={buildGoogleCalendarHref(event)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="secondary"
|
||||
iconStart={
|
||||
<img
|
||||
src="/assets/icons/google-calendar.svg"
|
||||
alt=""
|
||||
aria-hidden="true"
|
||||
height={20}
|
||||
/>
|
||||
}
|
||||
fullWidth
|
||||
>
|
||||
{t("actions.addToGoogleCalendar")}
|
||||
</Button>
|
||||
<Button
|
||||
as="a"
|
||||
href={`${detailPath}.ics`}
|
||||
@@ -436,7 +496,7 @@ export default function EventDetail() {
|
||||
variant="secondary"
|
||||
fullWidth
|
||||
>
|
||||
{t("actions.addToCalendar")}
|
||||
{t("actions.downloadIcsFile")}
|
||||
</Button>
|
||||
<p className="body-sm text-text-tertiary">
|
||||
{copy.eventDetail.addToCalendarHint}
|
||||
|
||||
Reference in New Issue
Block a user