Merged in feat/sentry (pull request #1089)

This commit is contained in:
Linus Flood
2024-12-19 10:35:20 +00:00
committed by Joakim Jäderberg
parent e0c5b59860
commit 3982b1ba56
32 changed files with 2715 additions and 429 deletions

View File

@@ -52,3 +52,6 @@ ENABLE_BOOKING_FLOW="false"
ENABLE_BOOKING_WIDGET="false" ENABLE_BOOKING_WIDGET="false"
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false" ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false"
SHOW_SITE_WIDE_ALERT="false" SHOW_SITE_WIDE_ALERT="false"
NEXT_PUBLIC_SENTRY_ENVIRONMENT="test"
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE="0"

2
.gitignore vendored
View File

@@ -47,3 +47,5 @@ certificates
# localfile with all the CSS variables exported from design system # localfile with all the CSS variables exported from design system
variables.css variables.css
# Sentry Config File
.env.sentry-build-plugin

View File

@@ -1,5 +1,5 @@
"use client" "use client"
import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react" import { useEffect } from "react"
export default function Error({ export default function Error({
@@ -8,7 +8,10 @@ export default function Error({
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
useEffect(() => { useEffect(() => {
if (!error) return
console.error({ breadcrumbsError: error }) console.error({ breadcrumbsError: error })
Sentry.captureException(error)
}, [error]) }, [error])
return ( return (

View File

@@ -1,5 +1,6 @@
"use client" "use client"
import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react" import { useEffect } from "react"
export default function Error({ export default function Error({
@@ -8,7 +9,10 @@ export default function Error({
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
useEffect(() => { useEffect(() => {
console.error({ breadcrumbsError: error }) if (!error) return
console.error(error)
Sentry.captureException(error)
}, [error]) }, [error])
return ( return (

View File

@@ -1,8 +1,17 @@
"use client" "use client"
import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react"
import type { ErrorPage } from "@/types/next/error" import type { ErrorPage } from "@/types/next/error"
export default function ProfileError({ error }: ErrorPage) { export default function ProfileError({ error }: ErrorPage) {
useEffect(() => {
if (!error) return
console.error(error) console.error(error)
Sentry.captureException(error)
}, [error])
return <h1>Error happened, Profile</h1> return <h1>Error happened, Profile</h1>
} }

View File

@@ -1,5 +1,5 @@
"use client" // Error components must be Client Components "use client" // Error components must be Client Components
import * as Sentry from "@sentry/nextjs"
import { import {
useParams, useParams,
usePathname, usePathname,
@@ -33,13 +33,17 @@ export default function Error({
const isFirstLoadRef = useRef<boolean>(true) const isFirstLoadRef = useRef<boolean>(true)
useEffect(() => { useEffect(() => {
// Log the error to an error reporting service if (!error) return
console.error(error) console.error(error)
if (error.message === SESSION_EXPIRED) { if (error.message === SESSION_EXPIRED) {
const loginUrl = login[params.lang] const loginUrl = login[params.lang]
window.location.assign(loginUrl) window.location.assign(loginUrl)
return
} }
Sentry.captureException(error)
}, [error, params.lang]) }, [error, params.lang])
useEffect(() => { useEffect(() => {

View File

@@ -1,5 +1,19 @@
"use client" "use client"
export default function Error() { import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react"
export default function Error({
error,
}: {
error: Error & { digest?: string }
}) {
useEffect(() => {
if (!error) return
console.error(error)
Sentry.captureException(error)
}, [error])
return null return null
} }

View File

@@ -1,5 +1,8 @@
"use client" "use client"
import * as Sentry from "@sentry/nextjs"
import { useEffect } from "react"
import styles from "./global-error.module.css" import styles from "./global-error.module.css"
export default function GlobalError({ export default function GlobalError({
@@ -8,6 +11,11 @@ export default function GlobalError({
error: Error & { digest?: string } error: Error & { digest?: string }
}) { }) {
console.log({ global_error: error }) console.log({ global_error: error })
useEffect(() => {
Sentry.captureException(error)
}, [error])
return ( return (
<html> <html>
<body> <body>

View File

@@ -1,4 +1,4 @@
import { LangRoute } from "@/types/routes" import type { LangRoute } from "@/types/routes"
export const signup: LangRoute = { export const signup: LangRoute = {
en: "/en/scandic-friends/join", en: "/en/scandic-friends/join",

5
env/client.ts vendored
View File

@@ -5,6 +5,8 @@ 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_SENTRY_ENVIRONMENT: z.string().default("development"),
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.01),
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE: z NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE: z
.string() .string()
// only allow "true" or "false" // only allow "true" or "false"
@@ -16,6 +18,9 @@ export const env = createEnv({
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_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE:
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE: NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE:
process.env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE, process.env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE,
}, },

4
env/server.ts vendored
View File

@@ -129,6 +129,8 @@ export const env = createEnv({
// transform to boolean // transform to boolean
.transform((s) => s === "true") .transform((s) => s === "true")
.default("false"), .default("false"),
SENTRY_ENVIRONMENT: z.string().default("development"),
SENTRY_SERVER_SAMPLERATE: z.coerce.number().default(0.01),
}, },
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
runtimeEnv: { runtimeEnv: {
@@ -193,5 +195,7 @@ export const env = createEnv({
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH: ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH:
process.env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH, process.env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH,
SHOW_SITE_WIDE_ALERT: process.env.SHOW_SITE_WIDE_ALERT, SHOW_SITE_WIDE_ALERT: process.env.SHOW_SITE_WIDE_ALERT,
SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
SENTRY_SERVER_SAMPLERATE: process.env.SENTRY_SERVER_SAMPLERATE,
}, },
}) })

View File

@@ -1,6 +1,15 @@
import * as Sentry from "@sentry/nextjs"
import { env } from "./env/server" import { env } from "./env/server"
export async function register() { export async function register() {
await configureSentry()
await configureApplicationInsights()
}
export const onRequestError = Sentry.captureRequestError
async function configureApplicationInsights() {
if ( if (
process.env.NEXT_RUNTIME === "nodejs" && process.env.NEXT_RUNTIME === "nodejs" &&
env.APPLICATION_INSIGHTS_CONNECTION_STRING env.APPLICATION_INSIGHTS_CONNECTION_STRING
@@ -11,11 +20,8 @@ export async function register() {
const { PeriodicExportingMetricReader } = await import( const { PeriodicExportingMetricReader } = await import(
"@opentelemetry/sdk-metrics" "@opentelemetry/sdk-metrics"
) )
const connectionString: string = env.APPLICATION_INSIGHTS_CONNECTION_STRING const connectionString: string = env.APPLICATION_INSIGHTS_CONNECTION_STRING
const traceExporter = new AzureMonitorTraceExporter({ connectionString }) const traceExporter = new AzureMonitorTraceExporter({ connectionString })
const azureMetricExporter = new AzureMonitorMetricExporter({ const azureMetricExporter = new AzureMonitorMetricExporter({
connectionString, connectionString,
}) })
@@ -23,7 +29,6 @@ export async function register() {
exporter: azureMetricExporter, exporter: azureMetricExporter,
exportIntervalMillis: 10000, exportIntervalMillis: 10000,
}) })
registerOTel({ registerOTel({
serviceName: "scandic-web", serviceName: "scandic-web",
traceExporter, traceExporter,
@@ -31,3 +36,14 @@ export async function register() {
}) })
} }
} }
async function configureSentry() {
switch (process.env.NEXT_RUNTIME) {
case "edge": {
await import("./sentry.edge.config")
}
case "nodejs": {
await import("./sentry.server.config")
}
}
}

View File

@@ -1,4 +1,5 @@
import { NextMiddleware, NextResponse } from "next/server" import * as Sentry from "@sentry/nextjs"
import { type NextMiddleware, NextResponse } from "next/server"
import { Lang } from "@/constants/languages" import { Lang } from "@/constants/languages"
@@ -63,20 +64,22 @@ export const middleware: NextMiddleware = async (request, event) => {
if (middleware.matcher(request)) { if (middleware.matcher(request)) {
const result = await middleware.middleware(request, event) const result = await middleware.middleware(request, event)
const _continue = result?.headers.get("x-continue") const _continue = result?.headers.get("x-continue")
if (_continue) { if (_continue) {
continue continue
} }
// Clean up internal headers // Clean up internal headers
result?.headers.delete("x-sh-origin") result?.headers.delete("x-sh-origin")
return result return result
} }
} }
} catch (e) { } catch (e) {
if (e instanceof NextResponse && e.status) { if (e instanceof NextResponse && e.status) {
const cause = await e.json() const cause = await e.json()
console.error(`NextResponse Error in middleware`) console.error(`NextResponse Error in middleware`, cause)
console.error(cause) Sentry.captureException(cause)
return NextResponse.rewrite( return NextResponse.rewrite(
new URL(`/${lang}/middleware-error/${e.status}`, request.nextUrl), new URL(`/${lang}/middleware-error/${e.status}`, request.nextUrl),
@@ -90,8 +93,9 @@ export const middleware: NextMiddleware = async (request, event) => {
) )
} }
console.error(`Error in middleware`) console.error(`Error in middleware`, e)
console.error(e) Sentry.captureException(e)
return NextResponse.rewrite( return NextResponse.rewrite(
new URL(`/${lang}/middleware-error/500`, request.nextUrl), new URL(`/${lang}/middleware-error/500`, request.nextUrl),
{ {

View File

@@ -1,4 +1,4 @@
import { NextResponse } from "next/server" import { type NextMiddleware, NextResponse } from "next/server"
import { notFound } from "@/server/errors/next" import { notFound } from "@/server/errors/next"
@@ -7,9 +7,7 @@ import { removeTrailingSlash } from "@/utils/url"
import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils" import { fetchAndCacheEntry, getDefaultRequestHeaders } from "./utils"
import type { NextMiddleware } from "next/server" import type { MiddlewareMatcher } from "@/types/middleware"
import { MiddlewareMatcher } from "@/types/middleware"
export const middleware: NextMiddleware = async (request) => { export const middleware: NextMiddleware = async (request) => {
const { nextUrl } = request const { nextUrl } = request

View File

@@ -1,6 +1,6 @@
import { NextMiddleware, NextResponse } from "next/server" import { type NextMiddleware, NextResponse } from "next/server"
import { MiddlewareMatcher } from "@/types/middleware" import type { MiddlewareMatcher } from "@/types/middleware"
/* /*
Middleware function to normalize date formats to support Middleware function to normalize date formats to support

View File

@@ -1,3 +1,4 @@
import * as Sentry from "@sentry/nextjs"
import createJiti from "jiti" import createJiti from "jiti"
import { fileURLToPath } from "url" import { fileURLToPath } from "url"
@@ -292,4 +293,23 @@ const nextConfig = {
}, },
} }
export default nextConfig export default Sentry.withSentryConfig(nextConfig, {
org: "scandic-hotels",
project: "scandic-web",
enabled: process.env.NODE_ENV !== "development",
authToken: process.env.SENTRY_AUTH_TOKEN,
// Only print logs for uploading source maps in CI
silent: !process.env.CI,
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// Automatically annotate React components to show their full name in breadcrumbs and session replay
reactComponentAnnotation: {
enabled: true,
},
hideSourceMaps: true,
disableLogger: true,
})

2860
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -24,11 +24,11 @@
"test:unit:watch": "jest --watch" "test:unit:watch": "jest --watch"
}, },
"dependencies": { "dependencies": {
"@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.24", "@azure/monitor-opentelemetry-exporter": "^1.0.0-beta.27",
"@contentstack/live-preview-utils": "^3.0.0", "@contentstack/live-preview-utils": "^3.0.0",
"@hookform/error-message": "^2.0.1", "@hookform/error-message": "^2.0.1",
"@hookform/resolvers": "^3.3.4", "@hookform/resolvers": "^3.3.4",
"@netlify/plugin-nextjs": "^5.1.1", "@netlify/plugin-nextjs": "^5.9.0",
"@opentelemetry/api": "^1.9.0", "@opentelemetry/api": "^1.9.0",
"@opentelemetry/sdk-metrics": "^1.25.1", "@opentelemetry/sdk-metrics": "^1.25.1",
"@radix-ui/react-dialog": "^1.1.1", "@radix-ui/react-dialog": "^1.1.1",
@@ -36,6 +36,7 @@
"@radix-ui/react-visually-hidden": "^1.1.0", "@radix-ui/react-visually-hidden": "^1.1.0",
"@react-aria/ssr": "^3.9.5", "@react-aria/ssr": "^3.9.5",
"@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9", "@scandic-hotels/design-system": "git+https://x-token-auth:$DESIGN_SYSTEM_ACCESS_TOKEN@bitbucket.org/scandic-swap/design-system.git#v0.1.0-rc.9",
"@sentry/nextjs": "^8.41.0",
"@t3-oss/env-nextjs": "^0.9.2", "@t3-oss/env-nextjs": "^0.9.2",
"@tanstack/react-query": "^5.28.6", "@tanstack/react-query": "^5.28.6",
"@tanstack/react-table": "^8.20.5", "@tanstack/react-table": "^8.20.5",

17
sentry.client.config.ts Normal file
View File

@@ -0,0 +1,17 @@
import * as Sentry from "@sentry/nextjs"
import { env } from "./env/client"
Sentry.init({
dsn: "https://fe39c070b4154e2f9cc35f0e5de0aedb@o4508102497206272.ingest.de.sentry.io/4508102500286544",
environment: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
enabled: env.NEXT_PUBLIC_SENTRY_ENVIRONMENT !== "development",
tracesSampleRate: env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
// Set profilesSampleRate to 1.0 to profile every transaction.
// Since profilesSampleRate is relative to tracesSampleRate,
// the final profiling rate can be computed as tracesSampleRate * profilesSampleRate
// For example, a tracesSampleRate of 0.5 and profilesSampleRate of 0.5 would
// result in 25% of transactions being profiled (0.5*0.5=0.25)
profilesSampleRate: 0.01,
})

11
sentry.edge.config.ts Normal file
View File

@@ -0,0 +1,11 @@
import * as Sentry from "@sentry/nextjs"
import { env } from "./env/server"
Sentry.init({
dsn: "https://fe39c070b4154e2f9cc35f0e5de0aedb@o4508102497206272.ingest.de.sentry.io/4508102500286544",
environment: env.SENTRY_ENVIRONMENT,
enabled: env.SENTRY_ENVIRONMENT !== "development",
tracesSampleRate: env.SENTRY_SERVER_SAMPLERATE,
})

10
sentry.server.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import * as Sentry from "@sentry/nextjs"
import { env } from "./env/server"
Sentry.init({
dsn: "https://fe39c070b4154e2f9cc35f0e5de0aedb@o4508102497206272.ingest.de.sentry.io/4508102500286544",
environment: env.SENTRY_ENVIRONMENT,
enabled: env.SENTRY_ENVIRONMENT !== "development",
tracesSampleRate: env.SENTRY_SERVER_SAMPLERATE,
})

View File

@@ -1,5 +1,5 @@
import { RoomData } from "@/types/hotel" import type { RoomData } from "@/types/hotel"
import { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation" import type { BookingConfirmation } from "@/types/trpc/routers/booking/confirmation"
export function getBookedHotelRoom( export function getBookedHotelRoom(
rooms: RoomData[] | undefined, rooms: RoomData[] | undefined,

View File

@@ -1,6 +1,5 @@
import { metrics } from "@opentelemetry/api" import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { import {
GetAccountPage, GetAccountPage,
GetAccountPageRefs, GetAccountPageRefs,
@@ -26,6 +25,7 @@ import type {
GetAccountPageRefsSchema, GetAccountPageRefsSchema,
GetAccountPageSchema, GetAccountPageSchema,
} from "@/types/trpc/routers/contentstack/accountPage" } from "@/types/trpc/routers/contentstack/accountPage"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.accountPage") const meter = metrics.getMeter("trpc.accountPage")

View File

@@ -1,4 +1,3 @@
import { Lang } from "@/constants/languages"
import { batchRequest } from "@/lib/graphql/batchRequest" import { batchRequest } from "@/lib/graphql/batchRequest"
import { import {
GetContentPage, GetContentPage,
@@ -19,6 +18,7 @@ import {
type TrackingSDKPageData, type TrackingSDKPageData,
} from "@/types/components/tracking" } from "@/types/components/tracking"
import type { GetContentPageSchema } from "@/types/trpc/routers/contentstack/contentPage" import type { GetContentPageSchema } from "@/types/trpc/routers/contentstack/contentPage"
import type { Lang } from "@/constants/languages"
export const contentPageQueryRouter = router({ export const contentPageQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => { get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {

View File

@@ -1,6 +1,5 @@
import { metrics } from "@opentelemetry/api" import { metrics } from "@opentelemetry/api"
import { Lang } from "@/constants/languages"
import { import {
GetLoyaltyPage, GetLoyaltyPage,
GetLoyaltyPageRefs, GetLoyaltyPageRefs,
@@ -20,12 +19,13 @@ import { getConnections } from "./utils"
import { import {
TrackingChannelEnum, TrackingChannelEnum,
TrackingSDKPageData, type TrackingSDKPageData,
} from "@/types/components/tracking" } from "@/types/components/tracking"
import type { import type {
GetLoyaltyPageRefsSchema, GetLoyaltyPageRefsSchema,
GetLoyaltyPageSchema, GetLoyaltyPageSchema,
} from "@/types/trpc/routers/contentstack/loyaltyPage" } from "@/types/trpc/routers/contentstack/loyaltyPage"
import type { Lang } from "@/constants/languages"
const meter = metrics.getMeter("trpc.loyaltyPage") const meter = metrics.getMeter("trpc.loyaltyPage")
// OpenTelemetry metrics: LoyaltyPage // OpenTelemetry metrics: LoyaltyPage

View File

@@ -1,3 +1,4 @@
import * as Sentry from "@sentry/node"
import { initTRPC } from "@trpc/server" import { initTRPC } from "@trpc/server"
import { experimental_nextAppDirCaller } from "@trpc/server/adapters/next-app-dir" import { experimental_nextAppDirCaller } from "@trpc/server/adapters/next-app-dir"
import { ZodError } from "zod" import { ZodError } from "zod"
@@ -36,9 +37,19 @@ const t = initTRPC
}, },
}) })
const sentryMiddleware = t.middleware(
Sentry.trpcMiddleware({
attachRpcInput: true,
})
)
export const { createCallerFactory, mergeRouters, router } = t export const { createCallerFactory, mergeRouters, router } = t
export const publicProcedure = t.procedure const baseProcedure = t.procedure.use(sentryMiddleware)
export const contentstackBaseProcedure = t.procedure.use(async function (opts) {
export const publicProcedure = baseProcedure
export const contentstackBaseProcedure = baseProcedure.use(
async function (opts) {
if (!opts.ctx.lang) { if (!opts.ctx.lang) {
// When fetching data client side with TRPC we don't pass through middlewares and therefore do not get the lang through headers // When fetching data client side with TRPC we don't pass through middlewares and therefore do not get the lang through headers
// We can then pass lang as an input in the request and set it to the context in the procedure // We can then pass lang as an input in the request and set it to the context in the procedure
@@ -60,7 +71,8 @@ export const contentstackBaseProcedure = t.procedure.use(async function (opts) {
lang: opts.ctx.lang, lang: opts.ctx.lang,
}, },
}) })
}) }
)
export const contentstackExtendedProcedureUID = contentstackBaseProcedure.use( export const contentstackExtendedProcedureUID = contentstackBaseProcedure.use(
async function (opts) { async function (opts) {
if (!opts.ctx.uid) { if (!opts.ctx.uid) {
@@ -74,7 +86,7 @@ export const contentstackExtendedProcedureUID = contentstackBaseProcedure.use(
}) })
} }
) )
export const protectedProcedure = t.procedure.use(async function (opts) { export const protectedProcedure = baseProcedure.use(async function (opts) {
const authRequired = opts.meta?.authRequired ?? true const authRequired = opts.meta?.authRequired ?? true
const session = await opts.ctx.auth() const session = await opts.ctx.auth()
if (!authRequired && env.NODE_ENV === "development") { if (!authRequired && env.NODE_ENV === "development") {
@@ -99,7 +111,7 @@ export const protectedProcedure = t.procedure.use(async function (opts) {
}) })
}) })
export const safeProtectedProcedure = t.procedure.use(async function (opts) { export const safeProtectedProcedure = baseProcedure.use(async function (opts) {
const authRequired = opts.meta?.authRequired ?? true const authRequired = opts.meta?.authRequired ?? true
let session: Session | null = await opts.ctx.auth() let session: Session | null = await opts.ctx.auth()
@@ -121,7 +133,7 @@ export const safeProtectedProcedure = t.procedure.use(async function (opts) {
}) })
}) })
export const serviceProcedure = t.procedure.use(async function (opts) { export const serviceProcedure = baseProcedure.use(async (opts) => {
const { access_token } = await getServiceToken() const { access_token } = await getServiceToken()
if (!access_token) { if (!access_token) {
throw internalServerError(`[serviceProcedure] No service token`) throw internalServerError(`[serviceProcedure] No service token`)
@@ -133,7 +145,7 @@ export const serviceProcedure = t.procedure.use(async function (opts) {
}) })
}) })
export const serverActionProcedure = t.procedure.experimental_caller( export const serverActionProcedure = baseProcedure.experimental_caller(
experimental_nextAppDirCaller({ experimental_nextAppDirCaller({
createContext, createContext,
normalizeFormData: true, normalizeFormData: true,

View File

@@ -1,14 +1,12 @@
import { z } from "zod" import type { z } from "zod"
import { import type {
priceSchema, priceSchema,
Product, Product,
productTypePriceSchema, productTypePriceSchema,
RoomConfiguration, RoomConfiguration,
} from "@/server/routers/hotels/output" } from "@/server/routers/hotels/output"
import type { RoomPackage } from "./roomFilter"
import { RoomPackage } from "./roomFilter"
import type { RateCode } from "./selectRate" import type { RateCode } from "./selectRate"
type ProductPrice = z.output<typeof productTypePriceSchema> type ProductPrice = z.output<typeof productTypePriceSchema>

View File

@@ -1,6 +1,5 @@
import { Product, RoomConfiguration } from "@/server/routers/hotels/output" import type { Product, RoomConfiguration } from "@/server/routers/hotels/output"
import type { ChildBedMapEnum } from "../../bookingWidget/enums"
import { ChildBedMapEnum } from "../../bookingWidget/enums"
export interface Child { export interface Child {
bed: ChildBedMapEnum bed: ChildBedMapEnum

View File

@@ -1,6 +1,6 @@
import { sidePanelVariants } from "@/components/HotelReservation/SidePanel/variants"
import type { VariantProps } from "class-variance-authority" import type { VariantProps } from "class-variance-authority"
import type { sidePanelVariants } from "@/components/HotelReservation/SidePanel/variants"
export interface SidePanelProps export interface SidePanelProps
extends VariantProps<typeof sidePanelVariants> {} extends VariantProps<typeof sidePanelVariants> {}

View File

@@ -1,6 +1,5 @@
import { MembershipLevel } from "@/constants/membershipLevels"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
import type { MembershipLevel } from "@/constants/membershipLevels"
export enum TrackingChannelEnum { export enum TrackingChannelEnum {
"scandic-friends" = "scandic-friends", "scandic-friends" = "scandic-friends",

View File

@@ -1,8 +1,7 @@
import { z } from "zod" import type { z } from "zod"
import { bookingConfirmationSchema } from "@/server/routers/booking/output" import type { Hotel, RoomData } from "@/types/hotel"
import type { bookingConfirmationSchema } from "@/server/routers/booking/output"
import { Hotel, RoomData } from "@/types/hotel"
export interface BookingConfirmationSchema export interface BookingConfirmationSchema
extends z.output<typeof bookingConfirmationSchema> {} extends z.output<typeof bookingConfirmationSchema> {}

View File

@@ -1,4 +1,4 @@
import { TrackingPosition, TrackingSDKData } from "@/types/components/tracking" import type { TrackingPosition, TrackingSDKData } from "@/types/components/tracking"
export function trackClick(name: string) { export function trackClick(name: string) {
pushToDataLayer({ pushToDataLayer({