Merged in monorepo-step-1 (pull request #1080)
Migrate to a monorepo setup - step 1 * Move web to subfolder /apps/scandic-web * Yarn + transitive deps - Move to yarn - design-system package removed for now since yarn doesn't support the parameter for token (ie project currently broken) - Add missing transitive dependencies as Yarn otherwise prevents these imports - VS Code doesn't pick up TS path aliases unless you open /apps/scandic-web instead of root (will be fixed with monorepo) * Pin framer-motion to temporarily fix typing issue https://github.com/adobe/react-spectrum/issues/7494 * Pin zod to avoid typ error There seems to have been a breaking change in the types returned by zod where error is now returned as undefined instead of missing in the type. We should just handle this but to avoid merge conflicts just pin the dependency for now. * Pin react-intl version Pin version of react-intl to avoid tiny type issue where formatMessage does not accept a generic any more. This will be fixed in a future commit, but to avoid merge conflicts just pin for now. * Pin typescript version Temporarily pin version as newer versions as stricter and results in a type error. Will be fixed in future commit after merge. * Setup workspaces * Add design-system as a monorepo package * Remove unused env var DESIGN_SYSTEM_ACCESS_TOKEN * Fix husky for monorepo setup * Update netlify.toml * Add lint script to root package.json * Add stub readme * Fix react-intl formatMessage types * Test netlify.toml in root * Remove root toml * Update netlify.toml publish path * Remove package-lock.json * Update build for branch/preview builds Approved-by: Linus Flood
This commit is contained in:
committed by
Linus Flood
parent
667cab6fb6
commit
80100e7631
101
apps/scandic-web/lib/trpc/Provider.tsx
Normal file
101
apps/scandic-web/lib/trpc/Provider.tsx
Normal file
@@ -0,0 +1,101 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
QueryCache,
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
} from "@tanstack/react-query"
|
||||
import { httpBatchLink, loggerLink, TRPCClientError } from "@trpc/client"
|
||||
import { useState } from "react"
|
||||
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
import { env } from "@/env/client"
|
||||
import { SessionExpiredError } from "@/server/errors/trpc"
|
||||
import { transformer } from "@/server/transformer"
|
||||
|
||||
import useLang from "@/hooks/useLang"
|
||||
|
||||
import { trpc } from "./client"
|
||||
|
||||
import type { AnyTRPCRouter } from "@trpc/server"
|
||||
|
||||
function initializeTrpcClient() {
|
||||
// Locally we set nextjs to run on port to 3000 so that we always guarantee
|
||||
// that trpc and next are running on the same port.
|
||||
return trpc.createClient({
|
||||
links: [
|
||||
loggerLink({
|
||||
enabled: (opts) =>
|
||||
(env.NEXT_PUBLIC_NODE_ENV === "development" &&
|
||||
typeof window !== "undefined") ||
|
||||
(opts.direction === "down" && opts.result instanceof Error),
|
||||
}),
|
||||
httpBatchLink({
|
||||
fetch,
|
||||
transformer,
|
||||
/**
|
||||
* This is locally in Next.js
|
||||
*/
|
||||
url: `/api/web/trpc`,
|
||||
}),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
export default function TrpcProvider({ children }: React.PropsWithChildren) {
|
||||
const lang = useLang()
|
||||
const [queryClient] = useState(
|
||||
() =>
|
||||
new QueryClient({
|
||||
queryCache: new QueryCache({
|
||||
async onError(error) {
|
||||
if (error instanceof TRPCClientError) {
|
||||
const appError: TRPCClientError<AnyTRPCRouter> = error
|
||||
console.log({ appError })
|
||||
if (appError.data?.code === "UNAUTHORIZED") {
|
||||
if (appError.data?.cause instanceof SessionExpiredError) {
|
||||
const loginUrl = login[lang]
|
||||
window.location.assign(loginUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 60 * 1000,
|
||||
retry(failureCount, error) {
|
||||
if (error instanceof TRPCClientError) {
|
||||
const appError: TRPCClientError<AnyTRPCRouter> = error
|
||||
|
||||
// Do not retry query requests that got UNAUTHORIZED error.
|
||||
// It won't make a difference sending the same request again.
|
||||
|
||||
if (appError.data?.code) {
|
||||
if (
|
||||
[
|
||||
"UNAUTHORIZED",
|
||||
"INTERNAL_SERVER_ERROR",
|
||||
"FORBIDDEN",
|
||||
].includes(appError.data.code)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retry all client requests that fail (and are not handled above)
|
||||
// at most 3 times.
|
||||
return failureCount < 3
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
const [trpcClient] = useState(() => initializeTrpcClient())
|
||||
return (
|
||||
<trpc.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
</trpc.Provider>
|
||||
)
|
||||
}
|
||||
10
apps/scandic-web/lib/trpc/client.ts
Normal file
10
apps/scandic-web/lib/trpc/client.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { createTRPCReact } from "@trpc/react-query"
|
||||
|
||||
import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"
|
||||
|
||||
import type { AppRouter } from "@/server"
|
||||
|
||||
export const trpc = createTRPCReact<AppRouter>()
|
||||
|
||||
export type RouterInput = inferRouterInputs<AppRouter>
|
||||
export type RouterOutput = inferRouterOutputs<AppRouter>
|
||||
264
apps/scandic-web/lib/trpc/memoizedRequests/index.ts
Normal file
264
apps/scandic-web/lib/trpc/memoizedRequests/index.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { getLang } from "@/i18n/serverContext"
|
||||
import { cache } from "@/utils/cache"
|
||||
|
||||
import { serverClient } from "../server"
|
||||
|
||||
import type { Country } from "@/types/enums/country"
|
||||
import type {
|
||||
AncillaryPackagesInput,
|
||||
BreackfastPackagesInput,
|
||||
PackagesInput,
|
||||
} from "@/types/requests/packages"
|
||||
import type {
|
||||
CityCoordinatesInput,
|
||||
HotelInput,
|
||||
} from "@/types/trpc/routers/hotel/hotel"
|
||||
import type { Lang } from "@/constants/languages"
|
||||
import type {
|
||||
GetHotelsByCSFilterInput,
|
||||
GetSelectedRoomAvailabilityInput,
|
||||
} from "@/server/routers/hotels/input"
|
||||
import type { GetSavedPaymentCardsInput } from "@/server/routers/user/input"
|
||||
|
||||
export const getLocations = cache(async function getMemoizedLocations() {
|
||||
return serverClient().hotel.locations.get()
|
||||
})
|
||||
|
||||
export const getProfile = cache(async function getMemoizedProfile() {
|
||||
return serverClient().user.get()
|
||||
})
|
||||
|
||||
export const getName = cache(async function getMemoizedName() {
|
||||
return serverClient().user.name()
|
||||
})
|
||||
|
||||
export const getProfileSafely = cache(
|
||||
async function getMemoizedProfileSafely() {
|
||||
return serverClient().user.getSafely()
|
||||
}
|
||||
)
|
||||
|
||||
export const getSavedPaymentCardsSafely = cache(
|
||||
async function getMemoizedSavedPaymentCardsSafely(
|
||||
input: GetSavedPaymentCardsInput
|
||||
) {
|
||||
return serverClient().user.safePaymentCards(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getMembershipLevel = cache(
|
||||
async function getMemoizedMembershipLevel() {
|
||||
return serverClient().user.membershipLevel()
|
||||
}
|
||||
)
|
||||
|
||||
export const getMembershipLevelSafely = cache(
|
||||
async function getMemoizedMembershipLevelSafely() {
|
||||
return serverClient().user.safeMembershipLevel()
|
||||
}
|
||||
)
|
||||
|
||||
export const getMembershipCards = cache(
|
||||
async function getMemoizedMembershipCards() {
|
||||
return serverClient().user.membershipCards()
|
||||
}
|
||||
)
|
||||
|
||||
export const getUserTracking = cache(async function getMemoizedUserTracking() {
|
||||
return serverClient().user.tracking()
|
||||
})
|
||||
|
||||
export const getHotelsByCSFilter = cache(async function getMemoizedHotels(
|
||||
input: GetHotelsByCSFilterInput
|
||||
) {
|
||||
return serverClient().hotel.hotels.byCSFilter.get(input)
|
||||
})
|
||||
|
||||
export const getHotel = cache(async function getMemoizedHotelData(
|
||||
input: HotelInput
|
||||
) {
|
||||
if (!input.isCardOnlyPayment) {
|
||||
input.isCardOnlyPayment = false
|
||||
}
|
||||
return serverClient().hotel.get(input)
|
||||
})
|
||||
|
||||
export const getHotelPage = cache(async function getMemoizedHotelPage() {
|
||||
return serverClient().contentstack.hotelPage.get()
|
||||
})
|
||||
|
||||
export const getSelectedRoomAvailability = cache(
|
||||
function getMemoizedSelectedRoomAvailability(
|
||||
input: GetSelectedRoomAvailabilityInput
|
||||
) {
|
||||
return serverClient().hotel.availability.room(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getFooter = cache(async function getMemoizedFooter() {
|
||||
return serverClient().contentstack.base.footer()
|
||||
})
|
||||
|
||||
export const getHeader = cache(async function getMemoizedHeader() {
|
||||
return serverClient().contentstack.base.header()
|
||||
})
|
||||
|
||||
export const getCurrentHeader = cache(async function getMemoizedCurrentHeader(
|
||||
lang: Lang
|
||||
) {
|
||||
return serverClient().contentstack.base.currentHeader({ lang })
|
||||
})
|
||||
|
||||
export const getCurrentFooter = cache(async function getMemoizedCurrentFooter(
|
||||
lang: Lang
|
||||
) {
|
||||
return serverClient().contentstack.base.currentFooter({ lang })
|
||||
})
|
||||
|
||||
export const getLanguageSwitcher = cache(
|
||||
async function getMemoizedLanguageSwitcher() {
|
||||
return serverClient().contentstack.languageSwitcher.get()
|
||||
}
|
||||
)
|
||||
|
||||
export const getSiteConfig = cache(async function getMemoizedSiteConfig(
|
||||
lang: Lang
|
||||
) {
|
||||
return serverClient().contentstack.base.siteConfig({ lang })
|
||||
})
|
||||
|
||||
export const getBreakfastPackages = cache(
|
||||
async function getMemoizedBreakfastPackages(input: BreackfastPackagesInput) {
|
||||
return serverClient().hotel.packages.breakfast(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getAncillaryPackages = cache(
|
||||
async function getMemoizedAncillaryPackages(input: AncillaryPackagesInput) {
|
||||
return serverClient().hotel.packages.ancillary(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getPackages = cache(async function getMemoizedPackages(
|
||||
input: PackagesInput
|
||||
) {
|
||||
return serverClient().hotel.packages.get(input)
|
||||
})
|
||||
|
||||
export const getBookingConfirmation = cache(
|
||||
async function getMemoizedBookingConfirmation(confirmationNumber: string) {
|
||||
return serverClient().booking.confirmation({ confirmationNumber })
|
||||
}
|
||||
)
|
||||
|
||||
export const getCityCoordinates = cache(
|
||||
async function getMemoizedCityCoordinates(input: CityCoordinatesInput) {
|
||||
return serverClient().hotel.map.city(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getCurrentRewards = cache(
|
||||
async function getMemoizedCurrentRewards() {
|
||||
return serverClient().contentstack.rewards.current()
|
||||
}
|
||||
)
|
||||
|
||||
export const getMeetingRooms = cache(
|
||||
async function getMemoizedMeetingRooms(input: {
|
||||
hotelId: string
|
||||
language: Lang
|
||||
}) {
|
||||
return serverClient().hotel.meetingRooms(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getAdditionalData = cache(
|
||||
async function getMemoizedAdditionalData(input: {
|
||||
hotelId: string
|
||||
language: Lang
|
||||
}) {
|
||||
return serverClient().hotel.additionalData(input)
|
||||
}
|
||||
)
|
||||
|
||||
export const getDestinationOverviewPage = cache(
|
||||
async function getMemoizedDestinationOverviewPage() {
|
||||
return serverClient().contentstack.destinationOverviewPage.get()
|
||||
}
|
||||
)
|
||||
export const getDestinationsList = cache(
|
||||
async function getMemoizedDestinationsList() {
|
||||
return serverClient().contentstack.destinationOverviewPage.destinations.get()
|
||||
}
|
||||
)
|
||||
export const getDestinationCountryPage = cache(
|
||||
async function getMemoizedDestinationCountryPage() {
|
||||
return serverClient().contentstack.destinationCountryPage.get()
|
||||
}
|
||||
)
|
||||
export const getDestinationCityPagesByCountry = cache(
|
||||
async function getMemoizedDestinationCityPagesByCountry(country: Country) {
|
||||
return serverClient().contentstack.destinationCountryPage.cityPages({
|
||||
country,
|
||||
})
|
||||
}
|
||||
)
|
||||
export const getHotelsByCountry = cache(
|
||||
async function getMemoizedHotelsByCountry(country: Country) {
|
||||
return serverClient().hotel.hotels.byCountry.get({
|
||||
country,
|
||||
})
|
||||
}
|
||||
)
|
||||
export const getHotelsByCityIdentifier = cache(
|
||||
async function getMemoizedHotelsByCityIdentifier(cityIdentifier: string) {
|
||||
return serverClient().hotel.hotels.byCityIdentifier.get({
|
||||
cityIdentifier,
|
||||
})
|
||||
}
|
||||
)
|
||||
export const getAllHotels = cache(async function getMemoizedAllHotels() {
|
||||
return serverClient().hotel.hotels.getAllHotels.get()
|
||||
})
|
||||
export const getDestinationCityPage = cache(
|
||||
async function getMemoizedDestinationCityPage() {
|
||||
return serverClient().contentstack.destinationCityPage.get()
|
||||
}
|
||||
)
|
||||
|
||||
export const getStartPage = cache(async function getMemoizedStartPage() {
|
||||
return serverClient().contentstack.startPage.get()
|
||||
})
|
||||
|
||||
export const getPageSettings = cache(async function getMemoizedPageSettings(
|
||||
lang: Lang
|
||||
) {
|
||||
return serverClient().contentstack.pageSettings.get({ lang })
|
||||
})
|
||||
|
||||
export const isBookingWidgetHidden = cache(
|
||||
async function isMemoizedBookingWidgetHidden() {
|
||||
const lang = getLang()
|
||||
const [pageSettingsResult, siteConfigResults] = await Promise.allSettled([
|
||||
getPageSettings(lang),
|
||||
getSiteConfig(lang),
|
||||
])
|
||||
|
||||
const pageSettings =
|
||||
pageSettingsResult.status === "fulfilled"
|
||||
? pageSettingsResult.value
|
||||
: null
|
||||
const siteConfig =
|
||||
siteConfigResults.status === "fulfilled" ? siteConfigResults.value : null
|
||||
|
||||
if (pageSettings) {
|
||||
return pageSettings.page.settings.hide_booking_widget
|
||||
}
|
||||
|
||||
if (siteConfig) {
|
||||
return siteConfig.bookingWidgetDisabled
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
)
|
||||
54
apps/scandic-web/lib/trpc/server.ts
Normal file
54
apps/scandic-web/lib/trpc/server.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { TRPCError } from "@trpc/server"
|
||||
import { redirect } from "next/navigation"
|
||||
|
||||
import { Lang } from "@/constants/languages"
|
||||
import { login } from "@/constants/routes/handleAuth"
|
||||
import { webviews } from "@/constants/routes/webviews"
|
||||
import { appRouter } from "@/server"
|
||||
import { createContext } from "@/server/context"
|
||||
import { createCallerFactory } from "@/server/trpc"
|
||||
|
||||
const createCaller = createCallerFactory(appRouter)
|
||||
|
||||
export function serverClient() {
|
||||
return createCaller(createContext(), {
|
||||
onError: ({ ctx, error, input, path, type }) => {
|
||||
console.error(`[serverClient] error for ${type}: ${path}`, error)
|
||||
|
||||
if (input) {
|
||||
console.error(`[serverClient] received input:`, input)
|
||||
}
|
||||
|
||||
if (error instanceof TRPCError) {
|
||||
if (error.code === "UNAUTHORIZED") {
|
||||
let lang = Lang.en
|
||||
let pathname = "/"
|
||||
let fullUrl = "/"
|
||||
|
||||
if (ctx) {
|
||||
lang = ctx.lang
|
||||
pathname = ctx.pathname
|
||||
fullUrl = ctx.url
|
||||
}
|
||||
|
||||
const fullPathname = new URL(fullUrl).pathname
|
||||
|
||||
if (webviews.includes(fullPathname)) {
|
||||
const redirectUrl = `/${lang}/webview/refresh?returnurl=${encodeURIComponent(fullPathname)}`
|
||||
console.error(
|
||||
"Unautorized in webview, redirecting to: ",
|
||||
redirectUrl
|
||||
)
|
||||
|
||||
console.log(`[serverClient] onError redirecting to: ${redirectUrl}`)
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
|
||||
const redirectUrl = `${login[lang]}?redirectTo=${encodeURIComponent(`/${lang}/${pathname}`)}`
|
||||
console.log(`[serverClient] onError redirecting to: ${redirectUrl}`)
|
||||
redirect(redirectUrl)
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user