Merged in fix/STAY-72-resend-booking-confirmation (pull request #3067)
feat(STAY-72): add resend confirmation button and endpoint * feat(STAY-72): add resend confirmation button and endpoint * fix: replace modify buttons with design system button Approved-by: Chuma Mcphoy (We Ahead) Approved-by: Erik Tiekstra
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.icon {
|
||||
padding-right: var(--Space-x05);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
"use client"
|
||||
|
||||
import { Button } from "@scandic-hotels/design-system/Button"
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
|
||||
import styles from "./actionsButton.module.css"
|
||||
|
||||
import type { MaterialSymbolProps } from "@scandic-hotels/design-system/Icons/MaterialIcon/MaterialSymbol"
|
||||
|
||||
export default function ActionsButton({
|
||||
icon,
|
||||
text,
|
||||
onPress,
|
||||
isDisabled = false,
|
||||
}: {
|
||||
icon: MaterialSymbolProps["icon"]
|
||||
text: string
|
||||
onPress: () => void
|
||||
isDisabled?: boolean
|
||||
}) {
|
||||
return (
|
||||
<Button
|
||||
variant="Text"
|
||||
wrapping={false}
|
||||
onPress={onPress}
|
||||
isDisabled={isDisabled}
|
||||
typography="Body/Paragraph/mdBold"
|
||||
>
|
||||
<MaterialIcon color="CurrentColor" icon={icon} className={styles.icon} />
|
||||
<span>{text}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
@@ -1,14 +1,10 @@
|
||||
"use client"
|
||||
|
||||
import { Button as ButtonRAC } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import styles from "./button.module.css"
|
||||
import ActionsButton from "../ActionsButton"
|
||||
|
||||
export default function AddToCalendarButton({
|
||||
disabled,
|
||||
@@ -25,20 +21,14 @@ export default function AddToCalendarButton({
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonRAC
|
||||
className={styles.button}
|
||||
<ActionsButton
|
||||
isDisabled={disabled}
|
||||
icon="calendar_add_on"
|
||||
text={intl.formatMessage({
|
||||
id: "common.addToCalendar",
|
||||
defaultMessage: "Add to calendar",
|
||||
})}
|
||||
onPress={handleAddToCalendar}
|
||||
>
|
||||
<MaterialIcon color="Icon/Interactive/Default" icon="calendar_add_on" />
|
||||
<Typography variant="Body/Paragraph/mdBold">
|
||||
<span className={styles.text}>
|
||||
{intl.formatMessage({
|
||||
id: "common.addToCalendar",
|
||||
defaultMessage: "Add to calendar",
|
||||
})}
|
||||
</span>
|
||||
</Typography>
|
||||
</ButtonRAC>
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
.button {
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
padding: var(--Space-x1) 0;
|
||||
width: 100%;
|
||||
|
||||
&:disabled {
|
||||
color: var(--Scandic-Grey-40);
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
color: var(--Text-Interactive-Default);
|
||||
text-align: left;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
import Alerts from "./Alerts"
|
||||
import Steps from "./Steps"
|
||||
|
||||
@@ -19,12 +20,14 @@ export default function CancelStay() {
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Modal.Button icon="cancel" onClick={trackCancelStay}>
|
||||
{intl.formatMessage({
|
||||
<ActionsButton
|
||||
icon="cancel"
|
||||
onPress={trackCancelStay}
|
||||
text={intl.formatMessage({
|
||||
id: "booking.cancelStay",
|
||||
defaultMessage: "Cancel stay",
|
||||
})}
|
||||
</Modal.Button>
|
||||
/>
|
||||
<Modal>
|
||||
<Dialog className={styles.dialog}>
|
||||
{({ close }) => (
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useMyStayStore } from "@/stores/my-stay"
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
import { dateHasPassed } from "../utils"
|
||||
import Alerts from "./Alerts"
|
||||
import Steps from "./Steps"
|
||||
@@ -40,13 +41,12 @@ export default function ChangeDates() {
|
||||
})
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Modal.Button
|
||||
<ActionsButton
|
||||
icon="edit_calendar"
|
||||
isDisabled={isDisabled}
|
||||
onClick={trackChangeDates}
|
||||
>
|
||||
{text}
|
||||
</Modal.Button>
|
||||
onPress={trackChangeDates}
|
||||
text={text}
|
||||
/>
|
||||
<Modal>
|
||||
<Dialog>
|
||||
{({ close }) => (
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
import { DialogTrigger } from "react-aria-components"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
import CustomerSupportModal from "@/components/HotelReservation/MyStay/ReferenceCard/Actions/CustomerSupportModal"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
|
||||
export default function CustomerSupport() {
|
||||
const intl = useIntl()
|
||||
|
||||
@@ -15,12 +16,14 @@ export default function CustomerSupport() {
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Modal.Button icon="support_agent" onClick={trackCustomerSupport}>
|
||||
{intl.formatMessage({
|
||||
<ActionsButton
|
||||
onPress={trackCustomerSupport}
|
||||
icon="support_agent"
|
||||
text={intl.formatMessage({
|
||||
id: "common.customerSupport",
|
||||
defaultMessage: "Customer support",
|
||||
})}
|
||||
</Modal.Button>
|
||||
/>
|
||||
<CustomerSupportModal />
|
||||
</DialogTrigger>
|
||||
)
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useMyStayStore } from "@/stores/my-stay"
|
||||
import Modal from "@/components/HotelReservation/MyStay/Modal"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
import { dateHasPassed } from "../utils"
|
||||
import Form from "./Form"
|
||||
|
||||
@@ -54,9 +55,11 @@ export default function GuaranteeLateArrival() {
|
||||
|
||||
return (
|
||||
<DialogTrigger>
|
||||
<Modal.Button icon="check" onClick={trackGuaranteeLateArrival}>
|
||||
{text}
|
||||
</Modal.Button>
|
||||
<ActionsButton
|
||||
onPress={trackGuaranteeLateArrival}
|
||||
text={text}
|
||||
icon="check"
|
||||
/>
|
||||
<Modal>
|
||||
<Dialog className={styles.dialog}>
|
||||
{({ close }) => (
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
"use client"
|
||||
import { useIntl } from "react-intl"
|
||||
|
||||
import { logger } from "@scandic-hotels/common/logger"
|
||||
import { toast } from "@scandic-hotels/design-system/Toast"
|
||||
import { trpc } from "@scandic-hotels/trpc/client"
|
||||
|
||||
import { useMyStayStore } from "@/stores/my-stay"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
import { trackMyStayPageLink } from "@/utils/tracking"
|
||||
|
||||
import ActionsButton from "../ActionsButton"
|
||||
|
||||
type ResendConfirmationEmailProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function ResendConfirmationEmail({
|
||||
onClose,
|
||||
}: ResendConfirmationEmailProps) {
|
||||
const intl = useIntl()
|
||||
const lang = useLang()
|
||||
|
||||
const refId = useMyStayStore((state) => state.refId)
|
||||
|
||||
const resendEmail = trpc.booking.resendConfirmation.useMutation()
|
||||
|
||||
function resendConfirmationEmail() {
|
||||
trackMyStayPageLink("resend confirmation email")
|
||||
resendEmail.mutate(
|
||||
{ language: lang, refId },
|
||||
{
|
||||
onSuccess() {
|
||||
onClose()
|
||||
toast.success(
|
||||
intl.formatMessage({
|
||||
id: "myStay.manageStay.resendConfirmationEmail.success",
|
||||
defaultMessage: "Confirmation email was resent successfully",
|
||||
})
|
||||
)
|
||||
},
|
||||
onError(e) {
|
||||
onClose()
|
||||
toast.error(
|
||||
intl.formatMessage({
|
||||
id: "myStay.manageStay.resendConfirmationEmail.error",
|
||||
defaultMessage:
|
||||
"There was an error resending the confirmation email",
|
||||
})
|
||||
)
|
||||
logger.error("[myStay] Resend confirmation email failed", {
|
||||
error: e.data,
|
||||
})
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const printMsg = intl.formatMessage({
|
||||
id: "myStay.manageStay.resendConfirmationEmail",
|
||||
defaultMessage: "Resend confirmation email",
|
||||
})
|
||||
|
||||
return (
|
||||
<ActionsButton
|
||||
onPress={resendConfirmationEmail}
|
||||
isDisabled={resendEmail.isPending}
|
||||
icon="email"
|
||||
text={printMsg}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
color: var(--Text-Interactive-Default);
|
||||
display: flex;
|
||||
gap: var(--Space-x1);
|
||||
padding: var(--Space-x1) 0;
|
||||
padding: var(--Space-x05) 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
.list {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: var(--Space-x15);
|
||||
}
|
||||
|
||||
@@ -3,16 +3,22 @@ import CancelStay from "./CancelStay"
|
||||
import ChangeDates from "./ChangeDates"
|
||||
import CustomerSupport from "./CustomerSupport"
|
||||
import GuaranteeLateArrival from "./GuaranteeLateArrival"
|
||||
import ResendConfirmationEmail from "./ResendConfirmationEmail"
|
||||
import ViewAndPrintReceipt from "./ViewAndPrintReceipt"
|
||||
|
||||
import styles from "./actions.module.css"
|
||||
|
||||
export default function Actions() {
|
||||
type ActionsProps = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function Actions({ onClose }: ActionsProps) {
|
||||
return (
|
||||
<div className={styles.list}>
|
||||
<ChangeDates />
|
||||
<GuaranteeLateArrival />
|
||||
<AddToCalendar />
|
||||
<ResendConfirmationEmail onClose={onClose} />
|
||||
<ViewAndPrintReceipt />
|
||||
<CustomerSupport />
|
||||
<CancelStay />
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function ManageStay() {
|
||||
</ButtonRAC>
|
||||
</header>
|
||||
<div className={styles.content}>
|
||||
<Actions />
|
||||
<Actions onClose={close} />
|
||||
<Info />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -78,6 +78,9 @@ export namespace endpoints {
|
||||
export function guarantee(confirmationNumber: string) {
|
||||
return `${bookings}/${confirmationNumber}/guarantee`
|
||||
}
|
||||
export function confirmNotification(confirmationNumber: string) {
|
||||
return `${bookings}/${confirmationNumber}/confirmNotification`
|
||||
}
|
||||
|
||||
export const enum Stays {
|
||||
future = `${base.path.booking}/${version}/${base.enitity.Stays}/future`,
|
||||
|
||||
@@ -22,6 +22,10 @@ export const removePackageInput = z.object({
|
||||
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
||||
})
|
||||
|
||||
export const resendConfirmationInput = z.object({
|
||||
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
||||
})
|
||||
|
||||
export const cancelBookingsInput = z.object({
|
||||
language: z.nativeEnum(Lang),
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
cancelBookingsInput,
|
||||
guaranteeBookingInput,
|
||||
removePackageInput,
|
||||
resendConfirmationInput,
|
||||
updateBookingInput,
|
||||
} from "../input"
|
||||
import { bookingConfirmationSchema } from "../output"
|
||||
@@ -318,6 +319,53 @@ export const bookingMutationRouter = router({
|
||||
|
||||
metricsRemovePackage.success()
|
||||
|
||||
return true
|
||||
}),
|
||||
resendConfirmation: safeProtectedServiceProcedure
|
||||
.input(resendConfirmationInput)
|
||||
.concat(refIdPlugin.toConfirmationNumber)
|
||||
.use(async ({ ctx, next }) => {
|
||||
const token = await ctx.getScandicUserToken()
|
||||
|
||||
return next({
|
||||
ctx: {
|
||||
token,
|
||||
},
|
||||
})
|
||||
})
|
||||
.mutation(async function ({ ctx, input }) {
|
||||
const { confirmationNumber } = ctx
|
||||
|
||||
const resendConfirmationCounter = createCounter(
|
||||
"trpc.booking",
|
||||
"confirmation.resend"
|
||||
)
|
||||
const metricsResendConfirmation = resendConfirmationCounter.init({
|
||||
confirmationNumber,
|
||||
})
|
||||
|
||||
metricsResendConfirmation.start()
|
||||
|
||||
const token = ctx.token ?? ctx.serviceToken
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
}
|
||||
|
||||
const apiResponse = await api.post(
|
||||
api.endpoints.v1.Booking.confirmNotification(confirmationNumber),
|
||||
{
|
||||
headers,
|
||||
},
|
||||
{ language: input.language }
|
||||
)
|
||||
|
||||
if (!apiResponse.ok) {
|
||||
await metricsResendConfirmation.httpError(apiResponse)
|
||||
return false
|
||||
}
|
||||
|
||||
metricsResendConfirmation.success()
|
||||
|
||||
return true
|
||||
}),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user