diff --git a/apps/partner-sas/app/page.tsx b/apps/partner-sas/app/page.tsx
index fc51ec18a..bd023bea4 100644
--- a/apps/partner-sas/app/page.tsx
+++ b/apps/partner-sas/app/page.tsx
@@ -1,15 +1,26 @@
import { Temp } from "@scandic-hotels/booking-flow/test-entry"
+import { Lang } from "@scandic-hotels/common/constants/language"
import { Typography } from "@scandic-hotels/design-system/Typography"
+import { serverClient } from "@/lib/trpc"
+
import styles from "./page.module.css"
-export default function Home() {
+export default async function Home() {
+ const caller = await serverClient()
+ const destinations = await caller.autocomplete.destinations({
+ lang: Lang.en,
+ includeTypes: ["hotels"],
+ query: "Göteborg",
+ })
+ const hotel = destinations.hits.hotels[0].name
+
return (
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
- hello world
+ hello world with data: {hotel}
{/* eslint-disable-next-line formatjs/no-literal-string-in-jsx */}
diff --git a/apps/partner-sas/global.d.ts b/apps/partner-sas/global.d.ts
new file mode 100644
index 000000000..9b2abb356
--- /dev/null
+++ b/apps/partner-sas/global.d.ts
@@ -0,0 +1,4 @@
+import "@scandic-hotels/common/global.d.ts"
+import "@scandic-hotels/trpc/types.d.ts"
+import "@scandic-hotels/trpc/auth.d.ts"
+import "@scandic-hotels/trpc/jwt.d.ts"
diff --git a/apps/partner-sas/lib/trpc.ts b/apps/partner-sas/lib/trpc.ts
new file mode 100644
index 000000000..4ba549d84
--- /dev/null
+++ b/apps/partner-sas/lib/trpc.ts
@@ -0,0 +1,34 @@
+import { headers } from "next/headers"
+
+import { createContext } from "@scandic-hotels/trpc/context"
+import {
+ appServerClient,
+ configureServerClient,
+} from "@scandic-hotels/trpc/serverClient"
+
+import type { Lang } from "@scandic-hotels/common/constants/language"
+
+export async function createAppContext() {
+ const headersList = await headers()
+
+ const ctx = createContext({
+ lang: headersList.get("x-lang") as Lang,
+ pathname: headersList.get("x-pathname")!,
+ uid: headersList.get("x-uid"),
+ url: headersList.get("x-url")!,
+ contentType: headersList.get("x-contenttype")!,
+ auth: async () => {
+ return null
+ },
+ })
+
+ return ctx
+}
+
+configureServerClient(createAppContext)
+
+export async function serverClient() {
+ const ctx = await createAppContext()
+
+ return appServerClient(ctx)
+}
diff --git a/apps/partner-sas/next.config.ts b/apps/partner-sas/next.config.ts
index 8b84ac803..c0ee08e9d 100644
--- a/apps/partner-sas/next.config.ts
+++ b/apps/partner-sas/next.config.ts
@@ -1,3 +1,5 @@
+import * as Sentry from "@sentry/nextjs"
+
import type { NextConfig } from "next"
const nextConfig: NextConfig = {
@@ -27,4 +29,23 @@ const nextConfig: NextConfig = {
},
}
-export default nextConfig
+export default Sentry.withSentryConfig(nextConfig, {
+ org: "scandic-hotels",
+ project: "partner-sas",
+
+ 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,
+})
diff --git a/apps/partner-sas/package.json b/apps/partner-sas/package.json
index 61cc0a254..016fcb6b1 100644
--- a/apps/partner-sas/package.json
+++ b/apps/partner-sas/package.json
@@ -18,6 +18,7 @@
"@netlify/plugin-nextjs": "^5.11.2",
"@scandic-hotels/booking-flow": "workspace:*",
"@scandic-hotels/design-system": "workspace:*",
+ "@sentry/nextjs": "^8.41.0",
"next": "15.3.4",
"react": "^19.0.0",
"react-dom": "^19.0.0"
diff --git a/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts b/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts
index b1eac0bd1..f4c96f5d8 100644
--- a/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts
+++ b/apps/scandic-web/app/api/web/trpc/[trpc]/route.ts
@@ -1,7 +1,8 @@
import { fetchRequestHandler } from "@trpc/server/adapters/fetch"
+import { appRouter } from "@scandic-hotels/trpc/routers/appRouter"
+
import { createAppContext } from "@/lib/trpc/server"
-import { appRouter } from "@/server"
async function handler(req: Request) {
return fetchRequestHandler({
diff --git a/apps/scandic-web/i18n/serverContext.ts b/apps/scandic-web/i18n/serverContext.ts
index 1b569e6bb..9e191b7a9 100644
--- a/apps/scandic-web/i18n/serverContext.ts
+++ b/apps/scandic-web/i18n/serverContext.ts
@@ -4,7 +4,6 @@ import { headers } from "next/headers"
import { cache } from "react"
import { Lang } from "@scandic-hotels/common/constants/language"
-
import { languageSchema } from "@scandic-hotels/common/utils/languages"
const getRef = cache(() => ({ current: undefined as Lang | undefined }))
diff --git a/apps/scandic-web/lib/trpc/client.ts b/apps/scandic-web/lib/trpc/client.ts
index c36594c58..c26280ca5 100644
--- a/apps/scandic-web/lib/trpc/client.ts
+++ b/apps/scandic-web/lib/trpc/client.ts
@@ -1,9 +1,8 @@
import { createTRPCReact } from "@trpc/react-query"
+import type { AppRouter } from "@scandic-hotels/trpc/routers/appRouter"
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"
-import type { AppRouter } from "@/server"
-
export const trpc = createTRPCReact()
export type RouterInput = inferRouterInputs
diff --git a/apps/scandic-web/lib/trpc/server.ts b/apps/scandic-web/lib/trpc/server.ts
index af66713bd..defc93404 100644
--- a/apps/scandic-web/lib/trpc/server.ts
+++ b/apps/scandic-web/lib/trpc/server.ts
@@ -1,22 +1,21 @@
-import * as Sentry from "@sentry/nextjs"
import { TRPCError } from "@trpc/server"
import { cookies, headers } from "next/headers"
import { redirect } from "next/navigation"
import { Lang } from "@scandic-hotels/common/constants/language"
-import { createCallerFactory } from "@scandic-hotels/trpc"
import { createContext } from "@scandic-hotels/trpc/context"
+import {
+ appServerClient,
+ configureServerClient,
+} from "@scandic-hotels/trpc/serverClient"
import { login } from "@/constants/routes/handleAuth"
import { webviews } from "@/constants/routes/webviews"
-import { appRouter } from "@/server"
import { auth } from "@/auth"
import type { Session } from "next-auth"
-const createCaller = createCallerFactory(appRouter)
-
export async function createAppContext() {
const headersList = await headers()
const cookie = await cookies()
@@ -49,17 +48,13 @@ export async function createAppContext() {
return ctx
}
+configureServerClient(createAppContext)
+
export async function serverClient() {
const ctx = await createAppContext()
- return createCaller(ctx, {
- onError: ({ ctx, error, input, path, type }) => {
- console.error(`[serverClient] error for ${type}: ${path}`, error)
-
- if (input) {
- console.error(`[serverClient] received input:`, input)
- }
-
+ return appServerClient(ctx, {
+ onError: ({ ctx, error }) => {
if (error instanceof TRPCError) {
if (error.code === "UNAUTHORIZED") {
let lang = Lang.en
@@ -90,19 +85,6 @@ export async function serverClient() {
redirect(redirectUrl)
}
}
-
- Sentry.captureException(error, {
- extra: {
- input,
- path,
- type,
- url: ctx?.url,
- lang: ctx?.lang,
- pathname: ctx?.pathname,
- contentType: ctx?.contentType,
- uid: ctx?.uid,
- },
- })
},
})
}
diff --git a/apps/scandic-web/package.json b/apps/scandic-web/package.json
index ee642e92f..04609c786 100644
--- a/apps/scandic-web/package.json
+++ b/apps/scandic-web/package.json
@@ -66,7 +66,6 @@
"@vercel/otel": "^1.12.0",
"@vis.gl/react-google-maps": "^1.5.2",
"class-variance-authority": "^0.7.1",
- "clean-deep": "^3.4.0",
"contentstack": "^3.25.3",
"date-fns": "^4.1.0",
"dayjs": "^1.11.13",
diff --git a/apps/scandic-web/server/index.ts b/apps/scandic-web/server/index.ts
deleted file mode 100644
index d3553b3e8..000000000
--- a/apps/scandic-web/server/index.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-/** Routers */
-import { router } from "@scandic-hotels/trpc"
-import { autocompleteRouter } from "@scandic-hotels/trpc/routers/autocomplete"
-import { bookingRouter } from "@scandic-hotels/trpc/routers/booking"
-import { contentstackRouter } from "@scandic-hotels/trpc/routers/contentstack"
-import { hotelsRouter } from "@scandic-hotels/trpc/routers/hotels"
-import { navigationRouter } from "@scandic-hotels/trpc/routers/navigation"
-import { partnerRouter } from "@scandic-hotels/trpc/routers/partners"
-import { userRouter } from "@scandic-hotels/trpc/routers/user"
-
-export const appRouter = router({
- booking: bookingRouter,
- contentstack: contentstackRouter,
- hotel: hotelsRouter,
- user: userRouter,
- partner: partnerRouter,
- navigation: navigationRouter,
- autocomplete: autocompleteRouter,
-})
-
-export type AppRouter = typeof appRouter
diff --git a/apps/scandic-web/server/routers/utils/index.ts b/apps/scandic-web/server/routers/utils/index.ts
deleted file mode 100644
index 01019aa41..000000000
--- a/apps/scandic-web/server/routers/utils/index.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import cleaner from "clean-deep"
-
-/**
- * Function to remove empty objects from a fetched content type.
- * Used since Contentstack returns empty objects for all non
- * queried in modular blocks.
- */
-export function removeEmptyObjects(obj: T) {
- return cleaner(obj, {
- emptyArrays: false,
- emptyStrings: false,
- nullValues: false,
- undefinedValues: false,
- })
-}
diff --git a/packages/booking-flow/global.d.ts b/packages/booking-flow/global.d.ts
new file mode 100644
index 000000000..9b2abb356
--- /dev/null
+++ b/packages/booking-flow/global.d.ts
@@ -0,0 +1,4 @@
+import "@scandic-hotels/common/global.d.ts"
+import "@scandic-hotels/trpc/types.d.ts"
+import "@scandic-hotels/trpc/auth.d.ts"
+import "@scandic-hotels/trpc/jwt.d.ts"
diff --git a/packages/booking-flow/lib/index.tsx b/packages/booking-flow/lib/index.tsx
index 81e53ff6c..a98b34865 100644
--- a/packages/booking-flow/lib/index.tsx
+++ b/packages/booking-flow/lib/index.tsx
@@ -1,13 +1,28 @@
+import { Lang } from "@scandic-hotels/common/constants/language"
import { Typography } from "@scandic-hotels/design-system/Typography"
import { env } from "../env/server"
+import { serverClient } from "./trpc"
const tempEnv = env.FOO
-export function Temp() {
+export async function Temp() {
+ const caller = await serverClient()
+ const destinations = await caller.autocomplete.destinations({
+ lang: Lang.en,
+ includeTypes: ["hotels"],
+ query: "Stockholm",
+ })
+ const hotel = destinations.hits.hotels[0].name
+
return (
-
- Tjena {tempEnv}
-
+
+
+ Tjena {tempEnv}
+
+
+ Something fetched by tRPC: {hotel}
+
+
)
}
diff --git a/packages/booking-flow/lib/trpc.ts b/packages/booking-flow/lib/trpc.ts
new file mode 100644
index 000000000..ad7cb355a
--- /dev/null
+++ b/packages/booking-flow/lib/trpc.ts
@@ -0,0 +1,3 @@
+import { packageServerClient } from "@scandic-hotels/trpc/serverClient"
+
+export const serverClient = packageServerClient
diff --git a/packages/trpc/lib/routers/appRouter.ts b/packages/trpc/lib/routers/appRouter.ts
new file mode 100644
index 000000000..1873e5576
--- /dev/null
+++ b/packages/trpc/lib/routers/appRouter.ts
@@ -0,0 +1,21 @@
+/** Routers */
+import { autocompleteRouter } from "../routers/autocomplete"
+import { bookingRouter } from "../routers/booking"
+import { contentstackRouter } from "../routers/contentstack"
+import { hotelsRouter } from "../routers/hotels"
+import { navigationRouter } from "../routers/navigation"
+import { partnerRouter } from "../routers/partners"
+import { userRouter } from "../routers/user"
+import { router } from ".."
+
+export const appRouter = router({
+ booking: bookingRouter,
+ contentstack: contentstackRouter,
+ hotel: hotelsRouter,
+ user: userRouter,
+ partner: partnerRouter,
+ navigation: navigationRouter,
+ autocomplete: autocompleteRouter,
+})
+
+export type AppRouter = typeof appRouter
diff --git a/packages/trpc/lib/serverClient.ts b/packages/trpc/lib/serverClient.ts
new file mode 100644
index 000000000..aad3b2d20
--- /dev/null
+++ b/packages/trpc/lib/serverClient.ts
@@ -0,0 +1,59 @@
+import * as Sentry from "@sentry/nextjs"
+
+import { appRouter } from "./routers/appRouter"
+import { createCallerFactory } from "."
+
+import type { Context } from "./context"
+
+const createCaller = createCallerFactory(appRouter)
+
+export type CreateContextFn = () => Promise
+let createTrpcContext: CreateContextFn | null = null
+
+export function configureServerClient(createContext: () => Promise) {
+ createTrpcContext = createContext
+}
+
+type OnError = Required>[1]["onError"]
+type ServerClientOptions = {
+ onError?: OnError
+}
+export function appServerClient(
+ context: Context,
+ options: ServerClientOptions = {}
+) {
+ return createCaller(context, {
+ onError: (args) => {
+ const { ctx, error, input, path, type } = args
+ console.error(`[serverClient] error for ${type}: ${path}`, error)
+ if (input) {
+ console.error(`[serverClient] received input:`, input)
+ }
+
+ options.onError?.(args)
+
+ Sentry.captureException(error, {
+ extra: {
+ input,
+ path,
+ type,
+ url: ctx?.url,
+ lang: ctx?.lang,
+ pathname: ctx?.pathname,
+ contentType: ctx?.contentType,
+ uid: ctx?.uid,
+ },
+ })
+ },
+ })
+}
+
+export async function packageServerClient() {
+ if (!createTrpcContext) {
+ throw new Error(
+ "createTrpcContext is not defined. Did you forget to call configureServerClient?"
+ )
+ }
+
+ return appServerClient(await createTrpcContext())
+}
diff --git a/packages/trpc/package.json b/packages/trpc/package.json
index d803e2fc3..732aa4173 100644
--- a/packages/trpc/package.json
+++ b/packages/trpc/package.json
@@ -17,6 +17,7 @@
"./errors": "./lib/errors.ts",
"./procedures": "./lib/procedures.ts",
"./transformer": "./lib/transformer.ts",
+ "./serverClient": "./lib/serverClient.ts",
"./utils/generateTag": "./lib/utils/generateTag.ts",
"./graphql/request": "./lib/graphql/request.ts",
"./graphql/batchRequest": "./lib/graphql/batchRequest.ts",
@@ -28,6 +29,7 @@
"./routers/partners/*": "./lib/routers/partners/*.ts",
"./routers/autocomplete/*": "./lib/routers/autocomplete/*.ts",
"./routers/navigation/*": "./lib/routers/navigation/*.ts",
+ "./routers/appRouter": "./lib/routers/appRouter.ts",
"./enums/*": "./lib/enums/*.ts",
"./types/*": "./lib/types/*.ts",
"./constants/*": "./lib/constants/*.ts",
diff --git a/yarn.lock b/yarn.lock
index f395ec36d..43093ee98 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6898,6 +6898,7 @@ __metadata:
"@scandic-hotels/common": "workspace:*"
"@scandic-hotels/design-system": "workspace:*"
"@scandic-hotels/typescript-config": "workspace:*"
+ "@sentry/nextjs": "npm:^8.41.0"
"@types/node": "npm:^20"
"@types/react": "npm:19.1.0"
"@types/react-dom": "npm:19.1.0"
@@ -7015,7 +7016,6 @@ __metadata:
adm-zip: "npm:^0.5.16"
babel-plugin-formatjs: "npm:^10.5.39"
class-variance-authority: "npm:^0.7.1"
- clean-deep: "npm:^3.4.0"
contentstack: "npm:^3.25.3"
cypress: "npm:^14.3.3"
date-fns: "npm:^4.1.0"
@@ -11017,17 +11017,6 @@ __metadata:
languageName: node
linkType: hard
-"clean-deep@npm:^3.4.0":
- version: 3.4.0
- resolution: "clean-deep@npm:3.4.0"
- dependencies:
- lodash.isempty: "npm:^4.4.0"
- lodash.isplainobject: "npm:^4.0.6"
- lodash.transform: "npm:^4.6.0"
- checksum: 10c0/cefb0fba6739724e8b49ef1bd4d4f05ca3072458e982ca82cadb9bee27db05dc02c17be8771e5b752a81696bef3ca86d883b2a39c2a2e8f91fa30057b8253ae0
- languageName: node
- linkType: hard
-
"clean-stack@npm:^2.0.0":
version: 2.2.0
resolution: "clean-stack@npm:2.2.0"
@@ -16114,13 +16103,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.isempty@npm:^4.4.0":
- version: 4.4.0
- resolution: "lodash.isempty@npm:4.4.0"
- checksum: 10c0/6c7eaa0802398736809b9e8aed8b8ac1abca9be71788fd719ba9d7f5b4c23e8dc63b7f049df4131713dda30a2fdedc2f655268e9deb8cd5a985dfc934afca194
- languageName: node
- linkType: hard
-
"lodash.isinteger@npm:^4.0.4":
version: 4.0.4
resolution: "lodash.isinteger@npm:4.0.4"
@@ -16170,13 +16152,6 @@ __metadata:
languageName: node
linkType: hard
-"lodash.transform@npm:^4.6.0":
- version: 4.6.0
- resolution: "lodash.transform@npm:4.6.0"
- checksum: 10c0/ad7f376b00dccff09f8597f19a171f2e074756178ae74887346876e10f6f0d83009460cc0793183cc5ee4e24a72ff86e68031c45b5aa2731c2f681a4dc93fe77
- languageName: node
- linkType: hard
-
"lodash@npm:^4.17.21, lodash@npm:~4.17.15":
version: 4.17.21
resolution: "lodash@npm:4.17.21"