diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css index bba79b2d0..341c3b979 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css +++ b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.module.css @@ -39,4 +39,4 @@ display: grid; grid-area: receipt; } -} +} \ No newline at end of file diff --git a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx index c8096f3e6..ea32a277c 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(confirmation)/booking-confirmation/page.tsx @@ -23,8 +23,6 @@ export default async function BookingConfirmationPage({ }: PageArgs) { setLang(params.lang) void getBookingConfirmation(searchParams.confirmationNumber) - const { confirmationNumber } = searchParams - return (
}> diff --git a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx index d934e1155..364f50349 100644 --- a/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx +++ b/app/[lang]/(live)/(public)/hotelreservation/(payment-callback)/payment-callback/page.tsx @@ -4,7 +4,6 @@ import { BOOKING_CONFIRMATION_NUMBER, PaymentErrorCodeEnum, } from "@/constants/booking" -import { Lang } from "@/constants/languages" import { bookingConfirmation, payment, diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/AddToCalendar.tsx b/components/HotelReservation/BookingConfirmation/Header/Actions/AddToCalendar.tsx new file mode 100644 index 000000000..a21055bee --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Header/Actions/AddToCalendar.tsx @@ -0,0 +1,61 @@ +"use client" +import { createEvent } from "ics" +import { useIntl } from "react-intl" + +import { dt } from "@/lib/dt" + +import { CalendarAddIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" +import useLang from "@/hooks/useLang" + +import type { AddToCalendarProps } from "@/types/components/hotelReservation/bookingConfirmation/actions/addToCalendar" + +export default function AddToCalendar({ + checkInDate, + event, + hotelName, +}: AddToCalendarProps) { + const intl = useIntl() + const lang = useLang() + + async function downloadBooking() { + const d = dt(checkInDate).locale(lang).format("YYYY-MM-DD") + const filename = `${hotelName.toLowerCase().split(" ").join("_")}-${d}.ics` + + const file: Blob = await new Promise((resolve, reject) => { + createEvent(event, (error, value) => { + if (error) { + reject(error) + } + + resolve(new File([value], filename, { type: "text/calendar" })) + }) + }) + + const url = URL.createObjectURL(file) + + const anchor = document.createElement("a") + anchor.href = url + anchor.download = filename + + document.body.appendChild(anchor) + anchor.click() + document.body.removeChild(anchor) + + URL.revokeObjectURL(url) + } + + return ( + + ) +} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/DownloadInvoice.tsx b/components/HotelReservation/BookingConfirmation/Header/Actions/DownloadInvoice.tsx new file mode 100644 index 000000000..3dea1f011 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Header/Actions/DownloadInvoice.tsx @@ -0,0 +1,27 @@ +"use client" +import { useIntl } from "react-intl" + +import { DownloadIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" + +export default function DownloadInvoice() { + const intl = useIntl() + + function downloadBooking() { + window.print() + } + + return ( + + ) +} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx b/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx new file mode 100644 index 000000000..d0c8c2a82 --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Header/Actions/ManageBooking.tsx @@ -0,0 +1,15 @@ +"use client" +import { useIntl } from "react-intl" + +import { EditIcon } from "@/components/Icons" +import Button from "@/components/TempDesignSystem/Button" + +export default function ManageBooking() { + const intl = useIntl() + return ( + + ) +} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css b/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css deleted file mode 100644 index c54e54d13..000000000 --- a/components/HotelReservation/BookingConfirmation/Header/Actions/actions.module.css +++ /dev/null @@ -1,15 +0,0 @@ -.actions { - border-radius: var(--Corner-radius-Medium); - display: grid; - grid-area: actions; - justify-content: flex-start; -} - -@media screen and (min-width: 768px) { - .actions { - gap: var(--Spacing-x3); - grid-auto-columns: auto; - grid-auto-flow: column; - grid-template-columns: auto; - } -} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/helpers.ts b/components/HotelReservation/BookingConfirmation/Header/Actions/helpers.ts new file mode 100644 index 000000000..7df5288bb --- /dev/null +++ b/components/HotelReservation/BookingConfirmation/Header/Actions/helpers.ts @@ -0,0 +1,15 @@ +import { dt } from "@/lib/dt" + +import type { DateTime } from "ics" + +export function generateDateTime(d: Date): DateTime { + const _d = dt(d).utc() + return [ + _d.year(), + // Need to add +1 since month is 0 based + _d.month() + 1, + _d.date(), + _d.hour(), + _d.minute(), + ] +} diff --git a/components/HotelReservation/BookingConfirmation/Header/Actions/index.tsx b/components/HotelReservation/BookingConfirmation/Header/Actions/index.tsx deleted file mode 100644 index e37296182..000000000 --- a/components/HotelReservation/BookingConfirmation/Header/Actions/index.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { CalendarAddIcon, DownloadIcon, EditIcon } from "@/components/Icons" -import Button from "@/components/TempDesignSystem/Button" -import { getIntl } from "@/i18n" - -import styles from "./actions.module.css" - -export default async function Actions() { - const intl = await getIntl() - return ( -
- - - -
- ) -} diff --git a/components/HotelReservation/BookingConfirmation/Header/header.module.css b/components/HotelReservation/BookingConfirmation/Header/header.module.css index 5468c7dbf..ebf136448 100644 --- a/components/HotelReservation/BookingConfirmation/Header/header.module.css +++ b/components/HotelReservation/BookingConfirmation/Header/header.module.css @@ -17,6 +17,22 @@ max-width: 720px; } +.actions { + border-radius: var(--Corner-radius-Medium); + display: grid; + grid-area: actions; + justify-content: flex-start; +} + +@media screen and (min-width: 768px) { + .actions { + gap: var(--Spacing-x3); + grid-auto-columns: auto; + grid-auto-flow: column; + grid-template-columns: auto; + } +} + @media screen and (min-width: 1367px) { .header { padding-bottom: var(--Spacing-x4); diff --git a/components/HotelReservation/BookingConfirmation/Header/index.tsx b/components/HotelReservation/BookingConfirmation/Header/index.tsx index bc2660c90..6cb144202 100644 --- a/components/HotelReservation/BookingConfirmation/Header/index.tsx +++ b/components/HotelReservation/BookingConfirmation/Header/index.tsx @@ -5,17 +5,22 @@ import Body from "@/components/TempDesignSystem/Text/Body" import Title from "@/components/TempDesignSystem/Text/Title" import { getIntl } from "@/i18n" -import Actions from "./Actions" +import AddToCalendar from "./Actions/AddToCalendar" +import DownloadInvoice from "./Actions/DownloadInvoice" +import { generateDateTime } from "./Actions/helpers" +import ManageBooking from "./Actions/ManageBooking" import styles from "./header.module.css" +import type { EventAttributes } from "ics" + import type { BookingConfirmationProps } from "@/types/components/hotelReservation/bookingConfirmation/bookingConfirmation" export default async function Header({ confirmationNumber, }: BookingConfirmationProps) { const intl = await getIntl() - const { hotel } = await getBookingConfirmation(confirmationNumber) + const { booking, hotel } = await getBookingConfirmation(confirmationNumber) const text = intl.formatMessage( { id: "booking.confirmation.text" }, @@ -28,6 +33,25 @@ export default async function Header({ } ) + const event: EventAttributes = { + busyStatus: "FREE", + categories: ["booking", "hotel", "stay"], + created: generateDateTime(booking.createDateTime), + description: hotel.hotelContent.texts.descriptions.medium, + end: generateDateTime(booking.checkOutDate), + endInputType: "utc", + geo: { + lat: hotel.location.latitude, + lon: hotel.location.longitude, + }, + location: `${hotel.address.streetAddress}, ${hotel.address.zipCode} ${hotel.address.city} ${hotel.address.country}`, + start: generateDateTime(booking.checkInDate), + startInputType: "utc", + status: "CONFIRMED", + title: hotel.name, + url: hotel.contactInformation.websiteUrl, + } + return (
@@ -39,7 +63,15 @@ export default async function Header({
{text} - +
+ + + +
) } diff --git a/package-lock.json b/package-lock.json index b4509615e..d42a1d4e8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", + "ics": "^3.8.1", "immer": "10.1.1", "json-stable-stringify-without-jsonify": "^1.0.1", "libphonenumber-js": "^1.10.60", @@ -11794,6 +11795,16 @@ "node": ">=0.10.0" } }, + "node_modules/ics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/ics/-/ics-3.8.1.tgz", + "integrity": "sha512-UqQlfkajfhrS4pUGQfGIJMYz/Jsl/ob3LqcfEhUmLbwumg+ZNkU0/6S734Vsjq3/FYNpEcZVKodLBoe+zBM69g==", + "dependencies": { + "nanoid": "^3.1.23", + "runes2": "^1.1.2", + "yup": "^1.2.0" + } + }, "node_modules/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", @@ -16840,6 +16851,11 @@ "react-is": "^16.13.1" } }, + "node_modules/property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -17859,6 +17875,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/runes2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/runes2/-/runes2-1.1.4.tgz", + "integrity": "sha512-LNPnEDPOOU4ehF71m5JoQyzT2yxwD6ZreFJ7MxZUAoMKNMY1XrAo60H1CUoX5ncSm0rIuKlqn9JZNRrRkNou2g==" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -19048,6 +19069,11 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "dev": true }, + "node_modules/tiny-case": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -19103,6 +19129,11 @@ "node": ">=0.6" } }, + "node_modules/toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, "node_modules/tough-cookie": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", @@ -20348,6 +20379,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/yup": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", + "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "dependencies": { + "property-expr": "^2.0.5", + "tiny-case": "^1.0.3", + "toposort": "^2.0.2", + "type-fest": "^2.19.0" + } + }, + "node_modules/yup/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.22.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", diff --git a/package.json b/package.json index efb824197..6f6f1f210 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "graphql": "^16.8.1", "graphql-request": "^6.1.0", "graphql-tag": "^2.12.6", + "ics": "^3.8.1", "immer": "10.1.1", "json-stable-stringify-without-jsonify": "^1.0.1", "libphonenumber-js": "^1.10.60", diff --git a/types/components/hotelReservation/bookingConfirmation/actions/addToCalendar.ts b/types/components/hotelReservation/bookingConfirmation/actions/addToCalendar.ts new file mode 100644 index 000000000..e24c1ffc7 --- /dev/null +++ b/types/components/hotelReservation/bookingConfirmation/actions/addToCalendar.ts @@ -0,0 +1,9 @@ +import type { EventAttributes } from "ics" + +import type { RouterOutput } from "@/lib/trpc/client" + +export interface AddToCalendarProps { + checkInDate: RouterOutput["booking"]["confirmation"]["booking"]["checkInDate"] + event: EventAttributes + hotelName: RouterOutput["booking"]["confirmation"]["hotel"]["name"] +}