feat(SW-864): add to calendar functionality
This commit is contained in:
@@ -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 (
|
||||
<Button
|
||||
intent="text"
|
||||
onPress={downloadBooking}
|
||||
size="small"
|
||||
theme="base"
|
||||
variant="icon"
|
||||
wrapping
|
||||
>
|
||||
<CalendarAddIcon />
|
||||
{intl.formatMessage({ id: "Add to calendar" })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<Button
|
||||
intent="text"
|
||||
onPress={downloadBooking}
|
||||
size="small"
|
||||
theme="base"
|
||||
variant="icon"
|
||||
wrapping
|
||||
>
|
||||
<DownloadIcon />
|
||||
{intl.formatMessage({ id: "Download invoice" })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -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 (
|
||||
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
|
||||
<EditIcon />
|
||||
{intl.formatMessage({ id: "Manage booking" })}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
]
|
||||
}
|
||||
@@ -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 (
|
||||
<div className={styles.actions}>
|
||||
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
|
||||
<CalendarAddIcon />
|
||||
{intl.formatMessage({ id: "Add to calendar" })}
|
||||
</Button>
|
||||
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
|
||||
<EditIcon />
|
||||
{intl.formatMessage({ id: "Manage booking" })}
|
||||
</Button>
|
||||
<Button intent="text" size="small" theme="base" variant="icon" wrapping>
|
||||
<DownloadIcon />
|
||||
{intl.formatMessage({ id: "Download invoice" })}
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<React.ReactNode>(
|
||||
{ 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 (
|
||||
<header className={styles.header}>
|
||||
<hgroup className={styles.hgroup}>
|
||||
@@ -39,7 +63,15 @@ export default async function Header({
|
||||
</Title>
|
||||
</hgroup>
|
||||
<Body className={styles.body}>{text}</Body>
|
||||
<Actions />
|
||||
<div className={styles.actions}>
|
||||
<AddToCalendar
|
||||
checkInDate={booking.checkInDate}
|
||||
event={event}
|
||||
hotelName={hotel.name}
|
||||
/>
|
||||
<ManageBooking />
|
||||
<DownloadInvoice />
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user