fix: avoid sending query params to planet
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
.layout {
|
||||||
|
background-color: var(--Base-Background-Primary-Normal);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { notFound } from "next/navigation"
|
||||||
|
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
|
import styles from "./layout.module.css"
|
||||||
|
|
||||||
|
import { LangParams, LayoutArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default function PaymentCallbackLayout({
|
||||||
|
children,
|
||||||
|
}: React.PropsWithChildren<LayoutArgs<LangParams>>) {
|
||||||
|
if (env.HIDE_FOR_NEXT_RELEASE) {
|
||||||
|
return notFound()
|
||||||
|
}
|
||||||
|
return <div className={styles.layout}>{children}</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { redirect } from "next/navigation"
|
||||||
|
|
||||||
|
import {
|
||||||
|
BOOKING_CONFIRMATION_NUMBER,
|
||||||
|
PaymentErrorCodeEnum,
|
||||||
|
} from "@/constants/booking"
|
||||||
|
import { Lang } from "@/constants/languages"
|
||||||
|
import {
|
||||||
|
bookingConfirmation,
|
||||||
|
payment,
|
||||||
|
} from "@/constants/routes/hotelReservation"
|
||||||
|
import { serverClient } from "@/lib/trpc/server"
|
||||||
|
|
||||||
|
import PaymentCallback from "@/components/HotelReservation/EnterDetails/Payment/PaymentCallback"
|
||||||
|
|
||||||
|
import { LangParams, PageArgs } from "@/types/params"
|
||||||
|
|
||||||
|
export default async function PaymentCallbackPage({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: PageArgs<
|
||||||
|
LangParams,
|
||||||
|
{ status: "error" | "success" | "cancel"; confirmationNumber?: string }
|
||||||
|
>) {
|
||||||
|
console.log(`[payment-callback] callback started`)
|
||||||
|
const lang = params.lang
|
||||||
|
const status = searchParams.status
|
||||||
|
const confirmationNumber = searchParams.confirmationNumber
|
||||||
|
|
||||||
|
if (status === "success" && confirmationNumber) {
|
||||||
|
const confirmationUrl = `${bookingConfirmation(lang)}?${BOOKING_CONFIRMATION_NUMBER}=${confirmationNumber}`
|
||||||
|
|
||||||
|
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
|
||||||
|
redirect(confirmationUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
const returnUrl = payment(lang)
|
||||||
|
const searchObject = new URLSearchParams()
|
||||||
|
|
||||||
|
if (confirmationNumber) {
|
||||||
|
try {
|
||||||
|
const bookingStatus = await serverClient().booking.status({
|
||||||
|
confirmationNumber,
|
||||||
|
})
|
||||||
|
if (bookingStatus.metadata) {
|
||||||
|
searchObject.set(
|
||||||
|
"errorCode",
|
||||||
|
bookingStatus.metadata.errorCode?.toString() ?? ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
||||||
|
)
|
||||||
|
if (status === "cancel") {
|
||||||
|
searchObject.set("errorCode", PaymentErrorCodeEnum.Cancelled.toString())
|
||||||
|
}
|
||||||
|
if (status === "error") {
|
||||||
|
searchObject.set("errorCode", PaymentErrorCodeEnum.Failed.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PaymentCallback
|
||||||
|
returnUrl={returnUrl.toString()}
|
||||||
|
searchObject={searchObject}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import { NextRequest, NextResponse } from "next/server"
|
|
||||||
|
|
||||||
import {
|
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
PaymentErrorCodeEnum,
|
|
||||||
} from "@/constants/booking"
|
|
||||||
import { Lang } from "@/constants/languages"
|
|
||||||
import {
|
|
||||||
bookingConfirmation,
|
|
||||||
payment,
|
|
||||||
} from "@/constants/routes/hotelReservation"
|
|
||||||
import { serverClient } from "@/lib/trpc/server"
|
|
||||||
import { getPublicURL } from "@/server/utils"
|
|
||||||
|
|
||||||
export async function GET(
|
|
||||||
request: NextRequest,
|
|
||||||
{ params }: { params: { lang: string; status: string } }
|
|
||||||
): Promise<NextResponse> {
|
|
||||||
const publicURL = getPublicURL(request)
|
|
||||||
|
|
||||||
console.log(`[payment-callback] callback started`)
|
|
||||||
const lang = params.lang as Lang
|
|
||||||
const status = params.status
|
|
||||||
|
|
||||||
const queryParams = request.nextUrl.searchParams
|
|
||||||
const confirmationNumber = queryParams.get(BOOKING_CONFIRMATION_NUMBER)
|
|
||||||
|
|
||||||
if (status === "success" && confirmationNumber) {
|
|
||||||
const confirmationUrl = new URL(`${publicURL}/${bookingConfirmation(lang)}`)
|
|
||||||
confirmationUrl.searchParams.set(
|
|
||||||
BOOKING_CONFIRMATION_NUMBER,
|
|
||||||
confirmationNumber
|
|
||||||
)
|
|
||||||
|
|
||||||
console.log(`[payment-callback] redirecting to: ${confirmationUrl}`)
|
|
||||||
return NextResponse.redirect(confirmationUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
const returnUrl = new URL(`${publicURL}/${payment(lang)}`)
|
|
||||||
returnUrl.search = queryParams.toString()
|
|
||||||
|
|
||||||
if (confirmationNumber) {
|
|
||||||
try {
|
|
||||||
const bookingStatus = await serverClient().booking.status({
|
|
||||||
confirmationNumber,
|
|
||||||
})
|
|
||||||
if (bookingStatus.metadata) {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
bookingStatus.metadata.errorCode?.toString() ?? ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(
|
|
||||||
`[payment-callback] failed to get booking status for ${confirmationNumber}, status: ${status}`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (status === "cancel") {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
PaymentErrorCodeEnum.Cancelled.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (status === "error") {
|
|
||||||
returnUrl.searchParams.set(
|
|
||||||
"errorCode",
|
|
||||||
PaymentErrorCodeEnum.Failed.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[payment-callback] redirecting to: ${returnUrl}`)
|
|
||||||
return NextResponse.redirect(returnUrl)
|
|
||||||
}
|
|
||||||
@@ -55,4 +55,8 @@ export const signedInDetailsSchema = z.object({
|
|||||||
firstName: z.string().optional(),
|
firstName: z.string().optional(),
|
||||||
lastName: z.string().optional(),
|
lastName: z.string().optional(),
|
||||||
phoneNumber: z.string().optional(),
|
phoneNumber: z.string().optional(),
|
||||||
|
join: z
|
||||||
|
.boolean()
|
||||||
|
.optional()
|
||||||
|
.transform((_) => false),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useEffect } from "react"
|
||||||
|
|
||||||
|
import { detailsStorageName } from "@/stores/details"
|
||||||
|
|
||||||
|
import { createQueryParamsForEnterDetails } from "@/components/HotelReservation/SelectRate/RoomSelection/utils"
|
||||||
|
import LoadingSpinner from "@/components/LoadingSpinner"
|
||||||
|
|
||||||
|
import { DetailsState } from "@/types/stores/details"
|
||||||
|
|
||||||
|
export default function PaymentCallback({
|
||||||
|
returnUrl,
|
||||||
|
searchObject,
|
||||||
|
}: {
|
||||||
|
returnUrl: string
|
||||||
|
searchObject: URLSearchParams
|
||||||
|
}) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const bookingData = window.sessionStorage.getItem(detailsStorageName)
|
||||||
|
|
||||||
|
if (bookingData) {
|
||||||
|
const detailsStorage: Record<
|
||||||
|
"state",
|
||||||
|
Pick<DetailsState, "data">
|
||||||
|
> = JSON.parse(bookingData)
|
||||||
|
const searchParams = createQueryParamsForEnterDetails(
|
||||||
|
detailsStorage.state.data.booking,
|
||||||
|
searchObject
|
||||||
|
)
|
||||||
|
|
||||||
|
if (searchParams.size > 0) {
|
||||||
|
router.replace(`${returnUrl}?${searchParams.toString()}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [returnUrl, router, searchObject])
|
||||||
|
|
||||||
|
return <LoadingSpinner />
|
||||||
|
}
|
||||||
@@ -60,7 +60,6 @@ export default function Payment({
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
const queryParams = useSearchParams()
|
|
||||||
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
const { booking, ...userData } = useDetailsStore((state) => state.data)
|
||||||
const setIsSubmittingDisabled = useDetailsStore(
|
const setIsSubmittingDisabled = useDetailsStore(
|
||||||
(state) => state.actions.setIsSubmittingDisabled
|
(state) => state.actions.setIsSubmittingDisabled
|
||||||
@@ -164,9 +163,6 @@ export default function Payment({
|
|||||||
])
|
])
|
||||||
|
|
||||||
function handleSubmit(data: PaymentFormData) {
|
function handleSubmit(data: PaymentFormData) {
|
||||||
const allQueryParams =
|
|
||||||
queryParams.size > 0 ? `?${queryParams.toString()}` : ""
|
|
||||||
|
|
||||||
// set payment method to card if saved card is submitted
|
// set payment method to card if saved card is submitted
|
||||||
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
const paymentMethod = isPaymentMethodEnum(data.paymentMethod)
|
||||||
? data.paymentMethod
|
? data.paymentMethod
|
||||||
@@ -176,6 +172,8 @@ export default function Payment({
|
|||||||
(card) => card.id === data.paymentMethod
|
(card) => card.id === data.paymentMethod
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const paymentRedirectUrl = `${env.NEXT_PUBLIC_NODE_ENV === "development" ? `http://localhost:${env.NEXT_PUBLIC_PORT}` : ""}/${lang}/hotelreservation/payment-callback`
|
||||||
|
|
||||||
initiateBooking.mutate({
|
initiateBooking.mutate({
|
||||||
hotelId: hotel,
|
hotelId: hotel,
|
||||||
checkInDate: fromDate,
|
checkInDate: fromDate,
|
||||||
@@ -224,9 +222,9 @@ export default function Payment({
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
success: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/success`,
|
success: `${paymentRedirectUrl}/success`,
|
||||||
error: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/error${allQueryParams}`,
|
error: `${paymentRedirectUrl}/error`,
|
||||||
cancel: `${env.NEXT_PUBLIC_PAYMENT_CALLBACK_URL}/${lang}/cancel${allQueryParams}`,
|
cancel: `${paymentRedirectUrl}/cancel`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,3 +54,50 @@ export function getQueryParamsForEnterDetails(
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createQueryParamsForEnterDetails(
|
||||||
|
bookingData: BookingData,
|
||||||
|
intitalSearchParams: URLSearchParams
|
||||||
|
) {
|
||||||
|
const { hotel, fromDate, toDate, rooms } = bookingData
|
||||||
|
|
||||||
|
const bookingSearchParams = new URLSearchParams({ hotel, fromDate, toDate })
|
||||||
|
const searchParams = new URLSearchParams([
|
||||||
|
...intitalSearchParams,
|
||||||
|
...bookingSearchParams,
|
||||||
|
])
|
||||||
|
|
||||||
|
rooms.forEach((item, index) => {
|
||||||
|
if (item?.adults) {
|
||||||
|
searchParams.set(`room[${index}].adults`, item.adults.toString())
|
||||||
|
}
|
||||||
|
if (item?.children) {
|
||||||
|
item.children.forEach((child, childIndex) => {
|
||||||
|
searchParams.set(
|
||||||
|
`room[${index}].child[${childIndex}].age`,
|
||||||
|
child.age.toString()
|
||||||
|
)
|
||||||
|
searchParams.set(
|
||||||
|
`room[${index}].child[${childIndex}].bed`,
|
||||||
|
child.bed.toString()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (item?.roomTypeCode) {
|
||||||
|
searchParams.set(`room[${index}].roomtype`, item.roomTypeCode)
|
||||||
|
}
|
||||||
|
if (item?.rateCode) {
|
||||||
|
searchParams.set(`room[${index}].ratecode`, item.rateCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item?.counterRateCode) {
|
||||||
|
searchParams.set(`room[${index}].counterratecode`, item.counterRateCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.packages && item.packages.length > 0) {
|
||||||
|
searchParams.set(`room[${index}].packages`, item.packages.join(","))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return searchParams
|
||||||
|
}
|
||||||
|
|||||||
4
env/client.ts
vendored
4
env/client.ts
vendored
@@ -5,14 +5,10 @@ export const env = createEnv({
|
|||||||
client: {
|
client: {
|
||||||
NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]),
|
NEXT_PUBLIC_NODE_ENV: z.enum(["development", "test", "production"]),
|
||||||
NEXT_PUBLIC_PORT: z.string().default("3000"),
|
NEXT_PUBLIC_PORT: z.string().default("3000"),
|
||||||
NEXT_PUBLIC_PAYMENT_CALLBACK_URL: z
|
|
||||||
.string()
|
|
||||||
.default("/api/web/payment-callback"),
|
|
||||||
},
|
},
|
||||||
emptyStringAsUndefined: true,
|
emptyStringAsUndefined: true,
|
||||||
runtimeEnv: {
|
runtimeEnv: {
|
||||||
NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV,
|
NEXT_PUBLIC_NODE_ENV: process.env.NODE_ENV,
|
||||||
NEXT_PUBLIC_PORT: process.env.NEXT_PUBLIC_PORT,
|
NEXT_PUBLIC_PORT: process.env.NEXT_PUBLIC_PORT,
|
||||||
NEXT_PUBLIC_PAYMENT_CALLBACK_URL: `${process.env.NODE_ENV === "development" ? `http://localhost:${process.env.NEXT_PUBLIC_PORT}` : ""}/api/web/payment-callback`,
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2,30 +2,14 @@ import "server-only"
|
|||||||
|
|
||||||
import deepmerge from "deepmerge"
|
import deepmerge from "deepmerge"
|
||||||
|
|
||||||
|
import { arrayMerge } from "@/utils/merge"
|
||||||
|
|
||||||
import { request } from "./request"
|
import { request } from "./request"
|
||||||
|
|
||||||
import type { BatchRequestDocument } from "graphql-request"
|
import type { BatchRequestDocument } from "graphql-request"
|
||||||
|
|
||||||
import type { Data } from "@/types/request"
|
import type { Data } from "@/types/request"
|
||||||
|
|
||||||
function arrayMerge(
|
|
||||||
target: any[],
|
|
||||||
source: any[],
|
|
||||||
options: deepmerge.ArrayMergeOptions | undefined
|
|
||||||
) {
|
|
||||||
const destination = target.slice()
|
|
||||||
source.forEach((item, index) => {
|
|
||||||
if (typeof destination[index] === "undefined") {
|
|
||||||
destination[index] = options?.cloneUnlessOtherwiseSpecified(item, options)
|
|
||||||
} else if (options?.isMergeableObject(item)) {
|
|
||||||
destination[index] = deepmerge(target[index], item, options)
|
|
||||||
} else if (target.indexOf(item) === -1) {
|
|
||||||
destination.push(item)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return destination
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function batchRequest<T>(
|
export async function batchRequest<T>(
|
||||||
queries: (BatchRequestDocument & { options?: RequestInit })[]
|
queries: (BatchRequestDocument & { options?: RequestInit })[]
|
||||||
): Promise<Data<T>> {
|
): Promise<Data<T>> {
|
||||||
|
|||||||
2
next-env.d.ts
vendored
2
next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
|||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
||||||
|
|||||||
@@ -282,6 +282,11 @@ const nextConfig = {
|
|||||||
"/:lang/hotelreservation/:step(breakfast|details|payment|select-bed)",
|
"/:lang/hotelreservation/:step(breakfast|details|payment|select-bed)",
|
||||||
destination: "/:lang/hotelreservation/step?step=:step",
|
destination: "/:lang/hotelreservation/step?step=:step",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: "/:lang/hotelreservation/payment-callback/:status",
|
||||||
|
destination:
|
||||||
|
"/:lang/hotelreservation/payment-callback?status=:status",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,13 +17,14 @@ export const createBookingSchema = z
|
|||||||
paymentUrl: z.string().nullable(),
|
paymentUrl: z.string().nullable(),
|
||||||
metadata: z
|
metadata: z
|
||||||
.object({
|
.object({
|
||||||
errorCode: z.number().optional(),
|
errorCode: z.number().nullable().optional(),
|
||||||
errorMessage: z.string().optional(),
|
errorMessage: z.string().nullable().optional(),
|
||||||
priceChangedMetadata: z
|
priceChangedMetadata: z
|
||||||
.object({
|
.object({
|
||||||
roomPrice: z.number().optional(),
|
roomPrice: z.number().nullable().optional(),
|
||||||
totalPrice: z.number().optional(),
|
totalPrice: z.number().nullable().optional(),
|
||||||
})
|
})
|
||||||
|
.nullable()
|
||||||
.optional(),
|
.optional(),
|
||||||
})
|
})
|
||||||
.nullable(),
|
.nullable(),
|
||||||
|
|||||||
@@ -11,11 +11,12 @@ import {
|
|||||||
signedInDetailsSchema,
|
signedInDetailsSchema,
|
||||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||||
import { DetailsContext } from "@/contexts/Details"
|
import { DetailsContext } from "@/contexts/Details"
|
||||||
|
import { arrayMerge } from "@/utils/merge"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type { DetailsState, InitialState } from "@/types/stores/details"
|
import type { DetailsState, InitialState } from "@/types/stores/details"
|
||||||
|
|
||||||
export const storageName = "details-storage"
|
export const detailsStorageName = "details-storage"
|
||||||
export function createDetailsStore(
|
export function createDetailsStore(
|
||||||
initialState: InitialState,
|
initialState: InitialState,
|
||||||
isMember: boolean
|
isMember: boolean
|
||||||
@@ -27,13 +28,15 @@ export function createDetailsStore(
|
|||||||
* we cannot use the data as `defaultValues` for our forms.
|
* we cannot use the data as `defaultValues` for our forms.
|
||||||
* RHF caches defaultValues on mount.
|
* RHF caches defaultValues on mount.
|
||||||
*/
|
*/
|
||||||
const detailsStorageUnparsed = sessionStorage.getItem(storageName)
|
const detailsStorageUnparsed = sessionStorage.getItem(detailsStorageName)
|
||||||
if (detailsStorageUnparsed) {
|
if (detailsStorageUnparsed) {
|
||||||
const detailsStorage: Record<
|
const detailsStorage: Record<
|
||||||
"state",
|
"state",
|
||||||
Pick<DetailsState, "data">
|
Pick<DetailsState, "data">
|
||||||
> = JSON.parse(detailsStorageUnparsed)
|
> = JSON.parse(detailsStorageUnparsed)
|
||||||
initialState = merge(initialState, detailsStorage.state.data)
|
initialState = merge(detailsStorage.state.data, initialState, {
|
||||||
|
arrayMerge,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return create<DetailsState>()(
|
return create<DetailsState>()(
|
||||||
@@ -140,7 +143,7 @@ export function createDetailsStore(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: storageName,
|
name: detailsStorageName,
|
||||||
onRehydrateStorage() {
|
onRehydrateStorage() {
|
||||||
return function (state) {
|
return function (state) {
|
||||||
if (state) {
|
if (state) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
} from "@/components/HotelReservation/EnterDetails/Details/schema"
|
||||||
import { StepsContext } from "@/contexts/Steps"
|
import { StepsContext } from "@/contexts/Steps"
|
||||||
|
|
||||||
import { storageName as detailsStorageName } from "./details"
|
import { detailsStorageName as detailsStorageName } from "./details"
|
||||||
|
|
||||||
import { StepEnum } from "@/types/enums/step"
|
import { StepEnum } from "@/types/enums/step"
|
||||||
import type { DetailsState } from "@/types/stores/details"
|
import type { DetailsState } from "@/types/stores/details"
|
||||||
|
|||||||
19
utils/merge.ts
Normal file
19
utils/merge.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import merge from "deepmerge"
|
||||||
|
|
||||||
|
export function arrayMerge(
|
||||||
|
target: any[],
|
||||||
|
source: any[],
|
||||||
|
options: merge.ArrayMergeOptions
|
||||||
|
) {
|
||||||
|
const destination = target.slice()
|
||||||
|
source.forEach((item, index) => {
|
||||||
|
if (typeof destination[index] === "undefined") {
|
||||||
|
destination[index] = options.cloneUnlessOtherwiseSpecified(item, options)
|
||||||
|
} else if (options?.isMergeableObject(item)) {
|
||||||
|
destination[index] = merge(target[index], item, options)
|
||||||
|
} else if (target.indexOf(item) === -1) {
|
||||||
|
destination.push(item)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return destination
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user