Merged in feat/SW-1371-delete-ancillary (pull request #1455)
feat(SW-1371): delete ancillary * feat(SW-1371): delete ancillary * Remove outline from dialog * Consistent return type from mutation * Error flow Approved-by: Michael Zetterberg Approved-by: Pontus Dreij
This commit is contained in:
@@ -29,6 +29,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
background-color: var(--Base-Surface-Primary-light-Normal);
|
background-color: var(--Base-Surface-Primary-light-Normal);
|
||||||
border-radius: var(--Corner-radius-Medium);
|
border-radius: var(--Corner-radius-Medium);
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function Dialog({
|
|||||||
{trigger}
|
{trigger}
|
||||||
<ModalOverlay className={styles.overlay} isDismissable>
|
<ModalOverlay className={styles.overlay} isDismissable>
|
||||||
<Modal>
|
<Modal>
|
||||||
<AriaDialog role="alertdialog">
|
<AriaDialog role="alertdialog" className={styles.dialog}>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<section className={styles.modal}>
|
<section className={styles.modal}>
|
||||||
<header className={styles.header}>
|
<header className={styles.header}>
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { trpc } from "@/lib/trpc/client"
|
||||||
|
|
||||||
|
import Dialog from "@/components/Dialog"
|
||||||
|
import { DeleteIcon } from "@/components/Icons"
|
||||||
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
|
import { toast } from "@/components/TempDesignSystem/Toasts"
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
export default function RemoveButton({
|
||||||
|
confirmationNumber,
|
||||||
|
code,
|
||||||
|
title,
|
||||||
|
onSuccess,
|
||||||
|
}: {
|
||||||
|
confirmationNumber: string
|
||||||
|
code: string
|
||||||
|
title?: string
|
||||||
|
onSuccess: () => void
|
||||||
|
}) {
|
||||||
|
const lang = useLang()
|
||||||
|
const intl = useIntl()
|
||||||
|
const removePackage = trpc.booking.removePackage.useMutation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
bodyText={intl.formatMessage({
|
||||||
|
id: "Are you sure you want to remove this product?",
|
||||||
|
})}
|
||||||
|
proceedText={intl.formatMessage({ id: "Remove" })}
|
||||||
|
proceedIsPending={removePackage.isPending}
|
||||||
|
cancelButtonText={intl.formatMessage({ id: "Cancel" })}
|
||||||
|
titleText={`${intl.formatMessage({ id: "Remove" })} ${title}`}
|
||||||
|
trigger={
|
||||||
|
<Button intent="text" size="small" variant="icon" theme="base">
|
||||||
|
<DeleteIcon />
|
||||||
|
{intl.formatMessage({ id: "Remove" })}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
proceedOnClick={(close) => {
|
||||||
|
removePackage.mutate(
|
||||||
|
{
|
||||||
|
language: lang,
|
||||||
|
confirmationNumber,
|
||||||
|
codes: [code],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
onSuccess: (data) => {
|
||||||
|
if (!data) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
close()
|
||||||
|
onSuccess()
|
||||||
|
},
|
||||||
|
onError: () => {
|
||||||
|
toast.error(intl.formatMessage({ id: "Something went wrong!" }))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
import { useRouter } from "next/navigation"
|
||||||
import { useIntl } from "react-intl"
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
import { CheckCircleIcon, DeleteIcon, EditIcon } from "@/components/Icons"
|
import { CheckCircleIcon, EditIcon } from "@/components/Icons"
|
||||||
import Accordion from "@/components/TempDesignSystem/Accordion"
|
import Accordion from "@/components/TempDesignSystem/Accordion"
|
||||||
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
|
import AccordionItem from "@/components/TempDesignSystem/Accordion/AccordionItem"
|
||||||
import Button from "@/components/TempDesignSystem/Button"
|
import Button from "@/components/TempDesignSystem/Button"
|
||||||
@@ -8,6 +9,8 @@ import Divider from "@/components/TempDesignSystem/Divider"
|
|||||||
import Body from "@/components/TempDesignSystem/Text/Body"
|
import Body from "@/components/TempDesignSystem/Text/Body"
|
||||||
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
import Subtitle from "@/components/TempDesignSystem/Text/Subtitle"
|
||||||
|
|
||||||
|
import RemoveButton from "./RemoveButton"
|
||||||
|
|
||||||
import styles from "./addedAncillaries.module.css"
|
import styles from "./addedAncillaries.module.css"
|
||||||
|
|
||||||
import type { AddedAncillariesProps } from "@/types/components/myPages/myStay/ancillaries"
|
import type { AddedAncillariesProps } from "@/types/components/myPages/myStay/ancillaries"
|
||||||
@@ -17,6 +20,7 @@ export function AddedAncillaries({
|
|||||||
booking,
|
booking,
|
||||||
}: AddedAncillariesProps) {
|
}: AddedAncillariesProps) {
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
@@ -76,30 +80,29 @@ export function AddedAncillaries({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.footerMobile}>
|
<div className={styles.footerMobile}>
|
||||||
<div className={styles.actions}>
|
{booking.confirmationNumber && ancillary.code ? (
|
||||||
<Button
|
<div className={styles.actions}>
|
||||||
intent="text"
|
<Button
|
||||||
size="small"
|
intent="text"
|
||||||
variant="icon"
|
size="small"
|
||||||
theme="base"
|
variant="icon"
|
||||||
>
|
theme="base"
|
||||||
<EditIcon />
|
>
|
||||||
{intl.formatMessage({ id: "Modify" })}
|
<EditIcon />
|
||||||
</Button>
|
{intl.formatMessage({ id: "Modify" })}
|
||||||
<Divider
|
</Button>
|
||||||
variant="vertical"
|
<Divider
|
||||||
color="baseSurfaceSubtleNormal"
|
variant="vertical"
|
||||||
/>
|
color="baseSurfaceSubtleNormal"
|
||||||
<Button
|
/>
|
||||||
intent="text"
|
<RemoveButton
|
||||||
size="small"
|
confirmationNumber={booking.confirmationNumber}
|
||||||
variant="icon"
|
code={ancillary.code}
|
||||||
theme="base"
|
title={ancillaryItem?.title}
|
||||||
>
|
onSuccess={() => router.refresh()}
|
||||||
<DeleteIcon />
|
/>
|
||||||
{intl.formatMessage({ id: "Remove" })}
|
</div>
|
||||||
</Button>
|
) : null}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
@@ -135,27 +138,29 @@ export function AddedAncillaries({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.actions}>
|
{booking.confirmationNumber && ancillary.code ? (
|
||||||
<Button
|
<div className={styles.actions}>
|
||||||
intent="text"
|
<Button
|
||||||
size="small"
|
intent="text"
|
||||||
variant="icon"
|
size="small"
|
||||||
theme="base"
|
variant="icon"
|
||||||
>
|
theme="base"
|
||||||
<EditIcon />
|
>
|
||||||
{intl.formatMessage({ id: "Modify" })}
|
<EditIcon />
|
||||||
</Button>
|
{intl.formatMessage({ id: "Modify" })}
|
||||||
<Divider variant="vertical" color="baseSurfaceSubtleNormal" />
|
</Button>
|
||||||
<Button
|
<Divider
|
||||||
intent="text"
|
variant="vertical"
|
||||||
size="small"
|
color="baseSurfaceSubtleNormal"
|
||||||
variant="icon"
|
/>
|
||||||
theme="base"
|
<RemoveButton
|
||||||
>
|
confirmationNumber={booking.confirmationNumber}
|
||||||
<DeleteIcon />
|
code={ancillary.code}
|
||||||
{intl.formatMessage({ id: "Remove" })}
|
title={ancillaryItem?.title}
|
||||||
</Button>
|
onSuccess={() => router.refresh()}
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
|
"Are you sure you want to remove this product?": "Er du sikker på, at du vil fjerne dette produkt?",
|
||||||
"/night per adult": "/nat per voksen",
|
"/night per adult": "/nat per voksen",
|
||||||
"<b>Included</b> (based on availability)": "<b>Inkluderet</b> (baseret på tilgængelighed)",
|
"<b>Included</b> (based on availability)": "<b>Inkluderet</b> (baseret på tilgængelighed)",
|
||||||
"<b>Total price</b> (incl VAT)": "<b>Samlet pris</b> (inkl. moms)",
|
"<b>Total price</b> (incl VAT)": "<b>Samlet pris</b> (inkl. moms)",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
|
"Are you sure you want to remove this product?": "Möchten Sie dieses Produkt wirklich entfernen?",
|
||||||
"/night per adult": "/Nacht pro Erwachsenem",
|
"/night per adult": "/Nacht pro Erwachsenem",
|
||||||
"<b>Included</b> (based on availability)": "<b>Inbegriffen</b> (je nach Verfügbarkeit)",
|
"<b>Included</b> (based on availability)": "<b>Inbegriffen</b> (je nach Verfügbarkeit)",
|
||||||
"<b>Total price</b> (incl VAT)": "<b>Gesamtpreis</b> (inkl. MwSt.)",
|
"<b>Total price</b> (incl VAT)": "<b>Gesamtpreis</b> (inkl. MwSt.)",
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"Are you sure you want to remove this product?": "Are you sure you want to remove this product?",
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
"/night per adult": "/night per adult",
|
"/night per adult": "/night per adult",
|
||||||
"<b>Included</b> (based on availability)": "<b>Included</b> (based on availability)",
|
"<b>Included</b> (based on availability)": "<b>Included</b> (based on availability)",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
|
"Are you sure you want to remove this product?": "Haluatko varmasti poistaa tämän tuotteen?",
|
||||||
"/night per adult": "/yötä aikuista kohti",
|
"/night per adult": "/yötä aikuista kohti",
|
||||||
"<b>Included</b> (based on availability)": "<b>Sisältyy</b> (saatavuuden mukaan)",
|
"<b>Included</b> (based on availability)": "<b>Sisältyy</b> (saatavuuden mukaan)",
|
||||||
"<b>Total price</b> (incl VAT)": "<b>Kokonaishinta</b> (sis. ALV)",
|
"<b>Total price</b> (incl VAT)": "<b>Kokonaishinta</b> (sis. ALV)",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
|
"Are you sure you want to remove this product?": "Er du sikker på at du vil fjerne dette produktet?",
|
||||||
"/night per adult": "/natt per voksen",
|
"/night per adult": "/natt per voksen",
|
||||||
"<b>Included</b> (based on availability)": "<b>Inkludert</b> (basert på tilgjengelighet)",
|
"<b>Included</b> (based on availability)": "<b>Inkludert</b> (basert på tilgjengelighet)",
|
||||||
"<b>Total price</b> (incl VAT)": "<b>Totalpris</b> (inkl. mva)",
|
"<b>Total price</b> (incl VAT)": "<b>Totalpris</b> (inkl. mva)",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"+46 8 517 517 00": "+46 8 517 517 00",
|
"+46 8 517 517 00": "+46 8 517 517 00",
|
||||||
|
"Are you sure you want to remove this product?": "Är du säker på att du vill ta bort den här produkten?",
|
||||||
"/night per adult": "/natt per vuxen",
|
"/night per adult": "/natt per vuxen",
|
||||||
"<b>Included</b> (based on availability)": "<b>Ingår</b> (baserat på tillgänglighet)",
|
"<b>Included</b> (based on availability)": "<b>Ingår</b> (baserat på tillgänglighet)",
|
||||||
"<b>Total price</b> (incl VAT)": "<b>Totalpris</b> (inkl moms)",
|
"<b>Total price</b> (incl VAT)": "<b>Totalpris</b> (inkl moms)",
|
||||||
|
|||||||
@@ -96,6 +96,12 @@ export const addPackageInput = z.object({
|
|||||||
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const removePackageInput = z.object({
|
||||||
|
confirmationNumber: z.string(),
|
||||||
|
codes: z.array(z.string()),
|
||||||
|
language: z.nativeEnum(Lang).transform((val) => langToApiLang[val]),
|
||||||
|
})
|
||||||
|
|
||||||
export const priceChangeInput = z.object({
|
export const priceChangeInput = z.object({
|
||||||
confirmationNumber: z.string(),
|
confirmationNumber: z.string(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ import { router, safeProtectedServiceProcedure } from "@/server/trpc"
|
|||||||
import { getMembership } from "@/utils/user"
|
import { getMembership } from "@/utils/user"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
cancelBookingInput,
|
|
||||||
addPackageInput,
|
addPackageInput,
|
||||||
|
cancelBookingInput,
|
||||||
createBookingInput,
|
createBookingInput,
|
||||||
priceChangeInput,
|
priceChangeInput,
|
||||||
|
removePackageInput,
|
||||||
} from "./input"
|
} from "./input"
|
||||||
import { createBookingSchema } from "./output"
|
import { createBookingSchema } from "./output"
|
||||||
|
|
||||||
@@ -48,6 +49,14 @@ const addPackageFailCounter = meter.createCounter(
|
|||||||
"trpc.bookings.add-package-fail"
|
"trpc.bookings.add-package-fail"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const removePackageCounter = meter.createCounter("trpc.bookings.remove-package")
|
||||||
|
const removePackageSuccessCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.remove-package-success"
|
||||||
|
)
|
||||||
|
const removePackageFailCounter = meter.createCounter(
|
||||||
|
"trpc.bookings.remove-package-fail"
|
||||||
|
)
|
||||||
|
|
||||||
async function getMembershipNumber(
|
async function getMembershipNumber(
|
||||||
session: Session | null
|
session: Session | null
|
||||||
): Promise<string | undefined> {
|
): Promise<string | undefined> {
|
||||||
@@ -379,4 +388,72 @@ export const bookingMutationRouter = router({
|
|||||||
|
|
||||||
return verifiedData.data
|
return verifiedData.data
|
||||||
}),
|
}),
|
||||||
|
removePackage: safeProtectedServiceProcedure
|
||||||
|
.input(removePackageInput)
|
||||||
|
.mutation(async function ({ ctx, input }) {
|
||||||
|
const accessToken = ctx.session?.token.access_token ?? ctx.serviceToken
|
||||||
|
const { confirmationNumber, codes, language } = input
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const loggingAttributes = {
|
||||||
|
confirmationNumber,
|
||||||
|
codes,
|
||||||
|
language,
|
||||||
|
}
|
||||||
|
|
||||||
|
removePackageCounter.add(1, loggingAttributes)
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
"api.booking.remove-package start",
|
||||||
|
JSON.stringify({
|
||||||
|
request: loggingAttributes,
|
||||||
|
headers,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const apiResponse = await api.remove(
|
||||||
|
api.endpoints.v1.Booking.packages(confirmationNumber),
|
||||||
|
{
|
||||||
|
headers,
|
||||||
|
} as RequestInit,
|
||||||
|
{ language, codes }
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
const text = await apiResponse.text()
|
||||||
|
removePackageFailCounter.add(1, {
|
||||||
|
confirmationNumber,
|
||||||
|
error_type: "http_error",
|
||||||
|
error: JSON.stringify({
|
||||||
|
status: apiResponse.status,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
console.error(
|
||||||
|
"api.booking.remove-package error",
|
||||||
|
JSON.stringify({
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
query: loggingAttributes,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
removePackageSuccessCounter.add(1, loggingAttributes)
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
"api.booking.remove-package success",
|
||||||
|
JSON.stringify({
|
||||||
|
query: loggingAttributes,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user