From 2eb374a41433864c895b030a3a62df5068aa9df9 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Wed, 3 Apr 2024 16:47:28 +0300 Subject: [PATCH 01/10] testing feature that allows users to add a specific booking to their calendar --- .../_layout+/bookings.$bookingId.cal[.ics].ts | 82 +++++++++++++++++++ app/routes/_layout+/bookings.$bookingId.tsx | 43 ++++++---- 2 files changed, 109 insertions(+), 16 deletions(-) create mode 100644 app/routes/_layout+/bookings.$bookingId.cal[.ics].ts diff --git a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts new file mode 100644 index 00000000..62cdf305 --- /dev/null +++ b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts @@ -0,0 +1,82 @@ +import { OrganizationRoles } from "@prisma/client"; +import { json, type LoaderFunctionArgs } from "@remix-run/node"; +import { z } from "zod"; +import { getBooking } from "~/modules/booking/service.server"; +import { makeShelfError, ShelfError } from "~/utils/error"; +import { error, getParams } from "~/utils/http.server"; +import { PermissionAction, PermissionEntity } from "~/utils/permissions/types"; +import { requirePermission } from "~/utils/roles.server"; + +export async function loader({ request, context, params }: LoaderFunctionArgs) { + const authSession = context.getSession(); + const { userId } = authSession; + const { bookingId } = getParams(params, z.object({ bookingId: z.string() }), { + additionalData: { userId }, + }); + + try { + /** Check if the current user is allowed to read booking */ + const { organizationId, role } = await requirePermission({ + userId: authSession.userId, + request, + entity: PermissionEntity.booking, + action: PermissionAction.read, + }); + const booking = await getBooking({ + id: bookingId, + organizationId: organizationId, + }); + + /** Check if the user is self service */ + const isSelfService = role === OrganizationRoles.SELF_SERVICE; + + /** For self service users, we only allow them to read their own bookings */ + if (isSelfService && booking.custodianUserId !== authSession.userId) { + throw new ShelfError({ + cause: null, + message: + "You are not authorized to download the calendar for this booking", + status: 403, + label: "Booking", + shouldBeCaptured: false, + }); + } + + const ics = ` +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//ZContent.net//Zap Calendar 1.0//EN +CALSCALE:GREGORIAN +METHOD:PUBLISH +BEGIN:VEVENT +SUMMARY:${booking.name} +UID:c7614cff-3549-4a00-9152-d25cc1fe077d +SEQUENCE:${Date.now()} +STATUS:CONFIRMED +TRANSP:TRANSPARENT +RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=12 +DTSTART:${booking.from} +DTEND:${booking.to} +DTSTAMP:${Date.now()} +CATEGORIES:U.S. Presidents,Civil War People +LOCATION:Hodgenville\, Kentucky +GEO:37.5739497;-85.7399606 +DESCRIPTION:Born February 12\, 1809\nSixteenth President (1861-1865)\n\n\n + \nhttp://AmericanHistoryCalendar.com +URL:http://americanhistorycalendar.com/peoplecalendar/1,328-abraham-lincol + n +END:VEVENT +END:VCALENDAR`.trim(); + + return new Response(ics, { + headers: { + // @TODO add caching headers + "Content-Type": "text/calendar", + "Content-Disposition": `attachment; filename="${booking.name}.ics"`, + }, + }); + } catch (cause) { + const reason = makeShelfError(cause, { userId }); + return json(error(reason), { status: reason.status }); + } +} diff --git a/app/routes/_layout+/bookings.$bookingId.tsx b/app/routes/_layout+/bookings.$bookingId.tsx index e6b6bed1..936312f3 100644 --- a/app/routes/_layout+/bookings.$bookingId.tsx +++ b/app/routes/_layout+/bookings.$bookingId.tsx @@ -15,7 +15,7 @@ import { NewBookingFormSchema } from "~/components/booking/form"; import ContextualModal from "~/components/layout/contextual-modal"; import Header from "~/components/layout/header"; import type { HeaderData } from "~/components/layout/header/types"; -import { Badge } from "~/components/shared"; +import { Badge, Button } from "~/components/shared"; import { db } from "~/database"; import { createNotes } from "~/modules/asset"; import { @@ -84,6 +84,17 @@ export async function loader({ context, request, params }: LoaderFunctionArgs) { organizationId: organizationId, }); + /** For self service users, we only allow them to read their own bookings */ + if (isSelfService && booking.custodianUserId !== authSession.userId) { + throw new ShelfError({ + cause: null, + message: "You are not authorized to view this booking", + status: 403, + label: "Booking", + shouldBeCaptured: false, + }); + } + const [teamMembers, org, assets] = await Promise.all([ /** * We need to fetch the team members to be able to display them in the custodian dropdown. @@ -169,16 +180,6 @@ export async function loader({ context, request, params }: LoaderFunctionArgs) { * This is useful for more consistent data in the front-end */ booking.assets = assets; - /** For self service users, we only allow them to read their own bookings */ - if (isSelfService && booking.custodianUserId !== authSession.userId) { - throw new ShelfError({ - cause: null, - message: "You are not authorized to view this booking", - status: 403, - label: "Booking", - }); - } - const { page, perPageParam } = getParamsValues(searchParams); const cookie = await updateCookieWithPerPage(request, perPageParam); const { perPage } = cookie; @@ -533,11 +534,21 @@ export default function BookingEditPage() {
- - {booking.status} - - +
+ + + {booking.status} + + + +
} /> From a073f242e3a2e245733b37a6355f3aa011b7a5ec Mon Sep 17 00:00:00 2001 From: Donkoko Date: Wed, 3 Apr 2024 19:18:28 +0300 Subject: [PATCH 02/10] adding minimum required data to event --- .../_layout+/bookings.$bookingId.cal[.ics].ts | 45 +++++++++++++------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts index 62cdf305..4d504038 100644 --- a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts +++ b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts @@ -2,6 +2,11 @@ import { OrganizationRoles } from "@prisma/client"; import { json, type LoaderFunctionArgs } from "@remix-run/node"; import { z } from "zod"; import { getBooking } from "~/modules/booking/service.server"; +import { + getClientHint, + getDateTimeFormatFromHints, +} from "~/utils/client-hints"; +import { SERVER_URL } from "~/utils/env"; import { makeShelfError, ShelfError } from "~/utils/error"; import { error, getParams } from "~/utils/http.server"; import { PermissionAction, PermissionEntity } from "~/utils/permissions/types"; @@ -41,6 +46,25 @@ export async function loader({ request, context, params }: LoaderFunctionArgs) { shouldBeCaptured: false, }); } + const hints = getClientHint(request); + const dateTimeFormat = getDateTimeFormatFromHints(hints, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }); + const fromDate = new Date( + dateTimeFormat.format(booking.from as Date) + ).toISOString(); + const toDate = new Date( + dateTimeFormat.format(booking.to as Date) + ).toISOString(); + // Remove the dashes, colons and decimal seconds from the ISO string + const formattedFromDate = fromDate.replace(/[-:]|\.\d{3}Z/g, ""); + const formattedToDate = toDate.replace(/[-:]|\.\d{3}Z/g, ""); const ics = ` BEGIN:VCALENDAR @@ -50,29 +74,24 @@ CALSCALE:GREGORIAN METHOD:PUBLISH BEGIN:VEVENT SUMMARY:${booking.name} -UID:c7614cff-3549-4a00-9152-d25cc1fe077d +UID:${booking.id} SEQUENCE:${Date.now()} STATUS:CONFIRMED TRANSP:TRANSPARENT -RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYMONTHDAY=12 -DTSTART:${booking.from} -DTEND:${booking.to} +DTSTART:${formattedFromDate} +DTEND:${formattedToDate} DTSTAMP:${Date.now()} -CATEGORIES:U.S. Presidents,Civil War People -LOCATION:Hodgenville\, Kentucky -GEO:37.5739497;-85.7399606 -DESCRIPTION:Born February 12\, 1809\nSixteenth President (1861-1865)\n\n\n - \nhttp://AmericanHistoryCalendar.com -URL:http://americanhistorycalendar.com/peoplecalendar/1,328-abraham-lincol - n +CATEGORIES:Shelf.nu booking +LOCATION:shelf.nu +DESCRIPTION:Shelf.nu booking (Asset / Equipment checkout) \n\n ${SERVER_URL}/bookings/${bookingId} +URL:${SERVER_URL}/bookings/${bookingId}/cal.ics END:VEVENT END:VCALENDAR`.trim(); return new Response(ics, { headers: { - // @TODO add caching headers "Content-Type": "text/calendar", - "Content-Disposition": `attachment; filename="${booking.name}.ics"`, + "Content-Disposition": `attachment; filename="${booking.name} - shelf.nu.ics"`, }, }); } catch (cause) { From 7b5a4815c337582f3efdd0fc8484b88416df27ed Mon Sep 17 00:00:00 2001 From: Donkoko Date: Wed, 3 Apr 2024 19:26:22 +0300 Subject: [PATCH 03/10] creating abstraction for date formatting --- .../_layout+/bookings.$bookingId.cal[.ics].ts | 27 ++++--------------- app/utils/date-fns.ts | 27 +++++++++++++++++++ 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts index 4d504038..b6586e5a 100644 --- a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts +++ b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts @@ -2,10 +2,8 @@ import { OrganizationRoles } from "@prisma/client"; import { json, type LoaderFunctionArgs } from "@remix-run/node"; import { z } from "zod"; import { getBooking } from "~/modules/booking/service.server"; -import { - getClientHint, - getDateTimeFormatFromHints, -} from "~/utils/client-hints"; +import { getClientHint } from "~/utils/client-hints"; +import { formatDatesForICal } from "~/utils/date-fns"; import { SERVER_URL } from "~/utils/env"; import { makeShelfError, ShelfError } from "~/utils/error"; import { error, getParams } from "~/utils/http.server"; @@ -47,24 +45,9 @@ export async function loader({ request, context, params }: LoaderFunctionArgs) { }); } const hints = getClientHint(request); - const dateTimeFormat = getDateTimeFormatFromHints(hints, { - year: "numeric", - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - hour12: false, - }); - const fromDate = new Date( - dateTimeFormat.format(booking.from as Date) - ).toISOString(); - const toDate = new Date( - dateTimeFormat.format(booking.to as Date) - ).toISOString(); - // Remove the dashes, colons and decimal seconds from the ISO string - const formattedFromDate = fromDate.replace(/[-:]|\.\d{3}Z/g, ""); - const formattedToDate = toDate.replace(/[-:]|\.\d{3}Z/g, ""); + + const formattedFromDate = formatDatesForICal(booking.from as Date, hints); + const formattedToDate = formatDatesForICal(booking.to as Date, hints); const ics = ` BEGIN:VCALENDAR diff --git a/app/utils/date-fns.ts b/app/utils/date-fns.ts index 385677c6..0c7f8ddf 100644 --- a/app/utils/date-fns.ts +++ b/app/utils/date-fns.ts @@ -1,3 +1,6 @@ +import type { ClientHint } from "~/modules/booking/types"; +import { getDateTimeFormatFromHints } from "./client-hints"; + export function getDifferenceInSeconds( dateLeft: Date, dateRight: Date @@ -51,3 +54,27 @@ export function getTimeRemainingMessage(date1: Date, date2: Date): string { return ""; //this should not happen } } + +export function formatDatesForICal(date: Date, hints: ClientHint) { + const dateTimeFormat = getDateTimeFormatFromHints(hints, { + year: "numeric", + month: "2-digit", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + hour12: false, + }); + const formatLocalDate = (date: Date, dateTimeFormat: Intl.DateTimeFormat) => { + const parts = dateTimeFormat.formatToParts(date); + const year = parts.find((part) => part.type === "year")!.value; + const month = parts.find((part) => part.type === "month")!.value; + const day = parts.find((part) => part.type === "day")!.value; + const hour = parts.find((part) => part.type === "hour")!.value; + const minute = parts.find((part) => part.type === "minute")!.value; + const second = parts.find((part) => part.type === "second")!.value; + return `${year}${month}${day}T${hour}${minute}${second}`; + }; + + return formatLocalDate(date, dateTimeFormat); +} From 97c8274420756da91e164df411cc7707d838a45b Mon Sep 17 00:00:00 2001 From: Donkoko Date: Thu, 4 Apr 2024 12:17:52 +0300 Subject: [PATCH 04/10] updating content based on feedback --- app/routes/_layout+/bookings.$bookingId.cal[.ics].ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts index b6586e5a..07dc58bc 100644 --- a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts +++ b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts @@ -66,8 +66,8 @@ DTEND:${formattedToDate} DTSTAMP:${Date.now()} CATEGORIES:Shelf.nu booking LOCATION:shelf.nu -DESCRIPTION:Shelf.nu booking (Asset / Equipment checkout) \n\n ${SERVER_URL}/bookings/${bookingId} -URL:${SERVER_URL}/bookings/${bookingId}/cal.ics +DESCRIPTION:Shelf.nu booking (Asset / Equipment checkout) +URL:${SERVER_URL}/bookings/${bookingId} END:VEVENT END:VCALENDAR`.trim(); From 991c5b7a33e49b5fb83c80d69ae6c69cd0abc0ca Mon Sep 17 00:00:00 2001 From: Donkoko Date: Thu, 4 Apr 2024 12:22:48 +0300 Subject: [PATCH 05/10] add tooltip --- app/routes/_layout+/bookings.$bookingId.tsx | 35 ++++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.tsx b/app/routes/_layout+/bookings.$bookingId.tsx index 936312f3..e05a2bc1 100644 --- a/app/routes/_layout+/bookings.$bookingId.tsx +++ b/app/routes/_layout+/bookings.$bookingId.tsx @@ -16,6 +16,12 @@ import ContextualModal from "~/components/layout/contextual-modal"; import Header from "~/components/layout/header"; import type { HeaderData } from "~/components/layout/header/types"; import { Badge, Button } from "~/components/shared"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "~/components/shared/tooltip"; import { db } from "~/database"; import { createNotes } from "~/modules/asset"; import { @@ -540,14 +546,7 @@ export default function BookingEditPage() { {booking.status} - + } /> @@ -578,3 +577,23 @@ export default function BookingEditPage() { ); } + +const AddToCalendar = () => ( + + + + + + +

Download this booking as a calendar event

+
+
+
+); From 0520e6b09b03d5008b5d35d9de830cb3ad49846c Mon Sep 17 00:00:00 2001 From: Donkoko Date: Thu, 4 Apr 2024 16:38:42 +0300 Subject: [PATCH 06/10] Disabling add-to-calendar when page is revalidating --- app/routes/_layout+/bookings.$bookingId.tsx | 47 ++++++++++++--------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.tsx b/app/routes/_layout+/bookings.$bookingId.tsx index e05a2bc1..d899621d 100644 --- a/app/routes/_layout+/bookings.$bookingId.tsx +++ b/app/routes/_layout+/bookings.$bookingId.tsx @@ -5,7 +5,7 @@ import type { MetaFunction, LoaderFunctionArgs, } from "@remix-run/node"; -import { useLoaderData } from "@remix-run/react"; +import { useLoaderData, useNavigation } from "@remix-run/react"; import { useAtomValue } from "jotai"; import { DateTime } from "luxon"; import { z } from "zod"; @@ -51,6 +51,7 @@ import { import { dateForDateTimeInputValue } from "~/utils/date-fns"; import { sendNotification } from "~/utils/emitter/send-notification.server"; import { ShelfError, makeShelfError } from "~/utils/error"; +import { isFormProcessing } from "~/utils/form"; import { PermissionAction, PermissionEntity } from "~/utils/permissions"; import { requirePermission } from "~/utils/roles.server"; import { bookingStatusColorMap } from "./bookings"; @@ -578,22 +579,28 @@ export default function BookingEditPage() { ); } -const AddToCalendar = () => ( - - - - - - -

Download this booking as a calendar event

-
-
-
-); +const AddToCalendar = () => { + const navigation = useNavigation(); + const disabled = isFormProcessing(navigation.state); + + return ( + + + + + + +

Download this booking as a calendar event

+
+
+
+ ); +}; From d99974b3227549de95e6f008b4aaa8abcf2f14aa Mon Sep 17 00:00:00 2001 From: Donkoko Date: Fri, 5 Apr 2024 09:27:21 +0300 Subject: [PATCH 07/10] move calendar button to proper location --- app/components/booking/form.tsx | 35 ++++++++++++++++++ app/components/shared/icons-map.tsx | 5 ++- app/routes/_layout+/bookings.$bookingId.tsx | 39 ++------------------- 3 files changed, 42 insertions(+), 37 deletions(-) diff --git a/app/components/booking/form.tsx b/app/components/booking/form.tsx index 6f8f64d8..52d5bdf0 100644 --- a/app/components/booking/form.tsx +++ b/app/components/booking/form.tsx @@ -8,6 +8,12 @@ import { useAtom } from "jotai"; import { useZorm } from "react-zorm"; import { z } from "zod"; import { updateDynamicTitleAtom } from "~/atoms/dynamic-title-atom"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "~/components/shared/tooltip"; import { useBookingStatus } from "~/hooks/use-booking-status"; import { useUserIsSelfService } from "~/hooks/user-user-is-self-service"; import type { BookingWithCustodians } from "~/routes/_layout+/bookings"; @@ -21,6 +27,7 @@ import { AbsolutePositionedHeaderActions } from "../layout/header/absolute-posit import { Button } from "../shared"; import { Card } from "../shared/card"; import { ControlledActionButton } from "../shared/controlled-action-button"; + /** * Important note is that the fields are only valudated when they are not disabled */ @@ -300,6 +307,7 @@ export function BookingForm({ assets during the duration of the booking period.

+ @@ -312,3 +320,30 @@ export function BookingForm({ ); } + +const AddToCalendar = () => { + const navigation = useNavigation(); + const disabled = isFormProcessing(navigation.state); + + return ( + + + + + + +

Download this booking as a calendar event

+
+
+
+ ); +}; diff --git a/app/components/shared/icons-map.tsx b/app/components/shared/icons-map.tsx index ba45aded..d28619a9 100644 --- a/app/components/shared/icons-map.tsx +++ b/app/components/shared/icons-map.tsx @@ -1,3 +1,4 @@ +import { CalendarIcon } from "@radix-ui/react-icons"; import { Spinner } from "./spinner"; import { @@ -57,7 +58,8 @@ export type Icon = | "help" | "profile" | "send" - | "user"; + | "user" + | "calendar"; type IconsMap = { [key in Icon]: JSX.Element; @@ -91,6 +93,7 @@ export const iconsMap: IconsMap = { logout: , send: , user: , + calendar: , }; export default iconsMap; diff --git a/app/routes/_layout+/bookings.$bookingId.tsx b/app/routes/_layout+/bookings.$bookingId.tsx index d899621d..4a00d668 100644 --- a/app/routes/_layout+/bookings.$bookingId.tsx +++ b/app/routes/_layout+/bookings.$bookingId.tsx @@ -5,7 +5,7 @@ import type { MetaFunction, LoaderFunctionArgs, } from "@remix-run/node"; -import { useLoaderData, useNavigation } from "@remix-run/react"; +import { useLoaderData } from "@remix-run/react"; import { useAtomValue } from "jotai"; import { DateTime } from "luxon"; import { z } from "zod"; @@ -15,13 +15,8 @@ import { NewBookingFormSchema } from "~/components/booking/form"; import ContextualModal from "~/components/layout/contextual-modal"; import Header from "~/components/layout/header"; import type { HeaderData } from "~/components/layout/header/types"; -import { Badge, Button } from "~/components/shared"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "~/components/shared/tooltip"; +import { Badge } from "~/components/shared"; + import { db } from "~/database"; import { createNotes } from "~/modules/asset"; import { @@ -51,7 +46,6 @@ import { import { dateForDateTimeInputValue } from "~/utils/date-fns"; import { sendNotification } from "~/utils/emitter/send-notification.server"; import { ShelfError, makeShelfError } from "~/utils/error"; -import { isFormProcessing } from "~/utils/form"; import { PermissionAction, PermissionEntity } from "~/utils/permissions"; import { requirePermission } from "~/utils/roles.server"; import { bookingStatusColorMap } from "./bookings"; @@ -547,7 +541,6 @@ export default function BookingEditPage() { {booking.status} - } /> @@ -578,29 +571,3 @@ export default function BookingEditPage() { ); } - -const AddToCalendar = () => { - const navigation = useNavigation(); - const disabled = isFormProcessing(navigation.state); - - return ( - - - - - - -

Download this booking as a calendar event

-
-
-
- ); -}; From fc89c2365304b4c5a6d813270af62cc310d824c5 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Fri, 5 Apr 2024 09:30:49 +0300 Subject: [PATCH 08/10] mend --- app/utils/date-fns.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/utils/date-fns.ts b/app/utils/date-fns.ts index bd9a0399..6efe9b1b 100644 --- a/app/utils/date-fns.ts +++ b/app/utils/date-fns.ts @@ -79,7 +79,6 @@ export function formatDatesForICal(date: Date, hints: ClientHint) { return formatLocalDate(date, dateTimeFormat); } - export function getBookingDefaultStartEndTimes() { const now = new Date(); From 6ef4d5601c68c6a7ca71228f14e9dcefa606d6ae Mon Sep 17 00:00:00 2001 From: Donkoko Date: Fri, 5 Apr 2024 10:29:46 +0300 Subject: [PATCH 09/10] fixing broken DTSTAMP value in ics file --- app/routes/_layout+/bookings.$bookingId.cal[.ics].ts | 3 ++- app/routes/_layout+/bookings.$bookingId.tsx | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts index 07dc58bc..0b7b0f2c 100644 --- a/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts +++ b/app/routes/_layout+/bookings.$bookingId.cal[.ics].ts @@ -48,6 +48,7 @@ export async function loader({ request, context, params }: LoaderFunctionArgs) { const formattedFromDate = formatDatesForICal(booking.from as Date, hints); const formattedToDate = formatDatesForICal(booking.to as Date, hints); + const formattedDTSTAMP = formatDatesForICal(new Date(Date.now()), hints); const ics = ` BEGIN:VCALENDAR @@ -63,7 +64,7 @@ STATUS:CONFIRMED TRANSP:TRANSPARENT DTSTART:${formattedFromDate} DTEND:${formattedToDate} -DTSTAMP:${Date.now()} +DTSTAMP:${formattedDTSTAMP} CATEGORIES:Shelf.nu booking LOCATION:shelf.nu DESCRIPTION:Shelf.nu booking (Asset / Equipment checkout) diff --git a/app/routes/_layout+/bookings.$bookingId.tsx b/app/routes/_layout+/bookings.$bookingId.tsx index 4a00d668..91647f7b 100644 --- a/app/routes/_layout+/bookings.$bookingId.tsx +++ b/app/routes/_layout+/bookings.$bookingId.tsx @@ -15,10 +15,10 @@ import { NewBookingFormSchema } from "~/components/booking/form"; import ContextualModal from "~/components/layout/contextual-modal"; import Header from "~/components/layout/header"; import type { HeaderData } from "~/components/layout/header/types"; -import { Badge } from "~/components/shared"; +import { Badge } from "~/components/shared/badge"; -import { db } from "~/database"; -import { createNotes } from "~/modules/asset"; +import { db } from "~/database/db.server"; +import { createNotes } from "~/modules/asset/service.server"; import { deleteBooking, getBooking, From 69c25c1a85c59f9a7b3e3e53834625b79af06bb3 Mon Sep 17 00:00:00 2001 From: Donkoko Date: Fri, 5 Apr 2024 12:32:06 +0300 Subject: [PATCH 10/10] disable add-to-calendar button when status doesnt allow it --- app/components/booking/form.tsx | 55 +++++++++++++++++---------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/app/components/booking/form.tsx b/app/components/booking/form.tsx index 52d5bdf0..fa3be95b 100644 --- a/app/components/booking/form.tsx +++ b/app/components/booking/form.tsx @@ -307,7 +307,9 @@ export function BookingForm({ assets during the duration of the booking period.

- + {!routeIsNewBooking && ( + + )} @@ -321,29 +323,28 @@ export function BookingForm({ ); } -const AddToCalendar = () => { - const navigation = useNavigation(); - const disabled = isFormProcessing(navigation.state); - - return ( - - - - - - -

Download this booking as a calendar event

-
-
-
- ); -}; +const AddToCalendar = ({ disabled }: { disabled: boolean }) => ( + + + + + + +

+ {disabled + ? "Not possible to add to calendar due to booking status" + : "Download this booking as a calendar event"} +

+
+
+
+);