Merged in feature/SW-2320-languagebased-hide-for-next-release (pull request #1937)

Language based alternative to HIDE_FOR_NEXT_RELEASE

Approved-by: Anton Gunnarsson
This commit is contained in:
Joakim Jäderberg
2025-05-05 10:53:28 +00:00
parent 3bcf6cff4a
commit 5784822a1e
41 changed files with 232 additions and 133 deletions

View File

@@ -54,7 +54,6 @@ GOOGLE_DYNAMIC_MAP_ID=""
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE="false" NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE="false"
ENABLE_BOOKING_FLOW="false"
ENABLE_BOOKING_WIDGET="false" ENABLE_BOOKING_WIDGET="false"
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false" ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false"
ENABLE_SURPRISES="true" ENABLE_SURPRISES="true"

View File

@@ -42,11 +42,11 @@ GOOGLE_STATIC_MAP_SIGNATURE_SECRET="test"
GOOGLE_STATIC_MAP_ID="test" GOOGLE_STATIC_MAP_ID="test"
GOOGLE_DYNAMIC_MAP_ID="test" GOOGLE_DYNAMIC_MAP_ID="test"
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE="true" NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE="true"
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE_LANG="en,sv,da,de,fi,no"
SALESFORCE_PREFERENCE_BASE_URL="test" SALESFORCE_PREFERENCE_BASE_URL="test"
USE_NEW_REWARD_MODEL="true" USE_NEW_REWARD_MODEL="true"
TZ=UTC TZ=UTC
ENABLE_BOOKING_FLOW="false"
ENABLE_BOOKING_WIDGET="false" ENABLE_BOOKING_WIDGET="false"
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false" ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH="false"
ENABLE_SURPRISES="true" ENABLE_SURPRISES="true"

View File

@@ -4,8 +4,9 @@ import CurrentLoadingSpinner from "@/components/Current/LoadingSpinner"
import LoadingSpinner from "@/components/LoadingSpinner" import LoadingSpinner from "@/components/LoadingSpinner"
export default function Loading() { export default function Loading() {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return <CurrentLoadingSpinner /> return <CurrentLoadingSpinner />
} }
return <LoadingSpinner /> return <LoadingSpinner />
} }

View File

@@ -13,8 +13,8 @@ export { generateMetadata } from "@/utils/generateMetadata"
export default function DestinationCityPagePage({ export default function DestinationCityPagePage({
searchParams, searchParams,
}: PageArgs<{}, { view?: "map" }>) { }: PageArgs<{}, { view?: "map" }>) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
notFound() return notFound()
} }
return ( return (

View File

@@ -6,16 +6,16 @@ import { env } from "@/env/server"
import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage" import DestinationCountryPage from "@/components/ContentType/DestinationPage/DestinationCountryPage"
import DestinationCountryPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCountryPage/DestinationCountryPageSkeleton" import DestinationCountryPageSkeleton from "@/components/ContentType/DestinationPage/DestinationCountryPage/DestinationCountryPageSkeleton"
import type { PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadata } from "@/utils/generateMetadata"
export default function DestinationCountryPagePage({}: PageArgs< export default function DestinationCountryPagePage({}: PageArgs<
{}, LangParams,
{ view?: "map" } { view?: "map" }
>) { >) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
notFound() return notFound()
} }
return ( return (

View File

@@ -4,10 +4,12 @@ import { env } from "@/env/server"
import DestinationOverviewPage from "@/components/ContentType/DestinationPage/DestinationOverviewPage" import DestinationOverviewPage from "@/components/ContentType/DestinationPage/DestinationOverviewPage"
import type { LangParams, PageArgs } from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata" export { generateMetadata } from "@/utils/generateMetadata"
export default function DestinationOverviewPagePage() { export default function DestinationOverviewPagePage({}: PageArgs<LangParams>) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return notFound() return notFound()
} }

View File

@@ -14,13 +14,14 @@ export { generateMetadata } from "@/utils/generateMetadata"
export default async function HotelPagePage({ export default async function HotelPagePage({
searchParams, searchParams,
}: PageArgs<{}, { subpage?: string; view?: "map" }>) { }: PageArgs<{}, { subpage?: string; view?: "map" }>) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return notFound() return notFound()
} }
const hotelPageData = await getHotelPage() const hotelPageData = await getHotelPage()
if (!hotelPageData) { if (!hotelPageData) {
notFound() return notFound()
} }
if (searchParams.subpage) { if (searchParams.subpage) {

View File

@@ -12,8 +12,9 @@ export { generateMetadata } from "@/utils/generateMetadata"
export default function StartPagePage({ export default function StartPagePage({
searchParams, searchParams,
}: PageArgs<{}, BookingWidgetSearchData>) { }: PageArgs<{}, BookingWidgetSearchData>) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
notFound() return notFound()
} }
return <StartPage searchParams={searchParams} /> return <StartPage searchParams={searchParams} />
} }

View File

@@ -11,7 +11,7 @@ import {
import type { LangParams, PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
export default function HotelReservationPage({ params }: PageArgs<LangParams>) { export default function HotelReservationPage({ params }: PageArgs<LangParams>) {
if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null return null
} }

View File

@@ -1,21 +1,22 @@
import { notFound } from "next/navigation"
import { env } from "@/env/server" import { env } from "@/env/server"
import type { Metadata } from "next" import type { LangParams, PageArgs } from "@/types/params"
export const metadata: Metadata = { export async function generateMetadata({ params }: PageArgs<LangParams>) {
return {
robots: { robots: {
index: false, index: env.isLangLive(params.lang),
follow: false, follow: env.isLangLive(params.lang),
}, },
} }
}
export default function HotelReservationLayout({ export default function HotelReservationLayout({
children, children,
}: React.PropsWithChildren) { }: React.PropsWithChildren) {
if (!env.ENABLE_BOOKING_FLOW) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return notFound() return null
} }
return <>{children}</> return <>{children}</>
} }

View File

@@ -175,7 +175,7 @@ export default async function MyStay({
hotel.galleryImages[0]?.imageSizes.large hotel.galleryImages[0]?.imageSizes.large
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
const promoUrl = env.HIDE_FOR_NEXT_RELEASE const promoUrl = !env.isLangLive(lang)
? new URL(getCurrentWebUrl({ path: "/", lang })) ? new URL(getCurrentWebUrl({ path: "/", lang }))
: new URL(`${baseUrl}/${lang}/`) : new URL(`${baseUrl}/${lang}/`)

View File

@@ -1,7 +1,7 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests" import { getDestinationCityPage } from "@/lib/trpc/memoizedRequests"
import BookingWidget from "@/components/BookingWidget" import { BookingWidget } from "@/components/BookingWidget"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget" import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params" import type { PageArgs } from "@/types/params"
@@ -9,9 +9,10 @@ import type { PageArgs } from "@/types/params"
export default async function BookingWidgetDestinationCityPage({ export default async function BookingWidgetDestinationCityPage({
searchParams, searchParams,
}: PageArgs<{}, BookingWidgetSearchData>) { }: PageArgs<{}, BookingWidgetSearchData>) {
if (!env.ENABLE_BOOKING_WIDGET) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null return null
} }
const { bookingCode } = searchParams const { bookingCode } = searchParams
const pageData = await getDestinationCityPage() const pageData = await getDestinationCityPage()

View File

@@ -1,7 +1,7 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests" import { getHotel, getHotelPage } from "@/lib/trpc/memoizedRequests"
import BookingWidget from "@/components/BookingWidget" import { BookingWidget } from "@/components/BookingWidget"
import { getLang } from "@/i18n/serverContext" import { getLang } from "@/i18n/serverContext"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget" import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
@@ -10,9 +10,10 @@ import type { PageArgs } from "@/types/params"
export default async function BookingWidgetHotelPage({ export default async function BookingWidgetHotelPage({
searchParams, searchParams,
}: PageArgs<{}, BookingWidgetSearchData & { subpage?: string }>) { }: PageArgs<{}, BookingWidgetSearchData & { subpage?: string }>) {
if (!env.ENABLE_BOOKING_WIDGET) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null return null
} }
const { bookingCode, subpage } = searchParams const { bookingCode, subpage } = searchParams
const hotelPageData = await getHotelPage() const hotelPageData = await getHotelPage()

View File

@@ -1,11 +0,0 @@
import { env } from "@/env/server"
import { BookingWidgetSkeleton } from "@/components/BookingWidget/Client"
export default function LoadingBookingWidget() {
if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) {
return null
}
return <BookingWidgetSkeleton />
}

View File

@@ -1,6 +1,6 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import BookingWidget from "@/components/BookingWidget" import { BookingWidget } from "@/components/BookingWidget"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget" import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { LangParams, PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
@@ -8,7 +8,7 @@ import type { LangParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage({ export default async function BookingWidgetPage({
searchParams, searchParams,
}: PageArgs<LangParams, BookingWidgetSearchData>) { }: PageArgs<LangParams, BookingWidgetSearchData>) {
if (!env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return null return null
} }

View File

@@ -1,11 +0,0 @@
import { env } from "@/env/server"
import { BookingWidgetSkeleton } from "@/components/BookingWidget/Client"
export default function LoadingBookingWidget() {
if (!env.ENABLE_BOOKING_WIDGET) {
return null
}
return <BookingWidgetSkeleton />
}

View File

@@ -1,14 +1,15 @@
import { env } from "@/env/server" import { env } from "@/env/server"
import BookingWidget from "@/components/BookingWidget" import { BookingWidget } from "@/components/BookingWidget"
import type { BookingWidgetSearchData } from "@/types/components/bookingWidget" import type { BookingWidgetSearchData } from "@/types/components/bookingWidget"
import type { PageArgs } from "@/types/params" import type { LangParams, PageArgs } from "@/types/params"
export default async function BookingWidgetPage({ export default async function BookingWidgetPage({
searchParams, searchParams,
}: PageArgs<{}, BookingWidgetSearchData>) { params,
if (!env.ENABLE_BOOKING_WIDGET) { }: PageArgs<LangParams, BookingWidgetSearchData>) {
if (!env.isLangLive(params.lang)) {
return null return null
} }

View File

@@ -1,21 +1,22 @@
import { notFound } from "next/navigation"
import { env } from "@/env/server" import { env } from "@/env/server"
import type { Metadata } from "next" import type { LangParams, PageArgs } from "@/types/params"
export const metadata: Metadata = { export async function generateMetadata({ params }: PageArgs<LangParams>) {
return {
robots: { robots: {
index: false, index: env.isLangLive(params.lang),
follow: false, follow: env.isLangLive(params.lang),
}, },
} }
}
export default function HotelReservationLayout({ export default function HotelReservationLayout({
children, children,
}: React.PropsWithChildren) { }: React.PropsWithChildren) {
if (!env.ENABLE_BOOKING_FLOW) { if (env.NEW_SITE_LIVE_STATUS === "NOT_LIVE") {
return notFound() return null
} }
return <>{children}</> return <>{children}</>
} }

View File

@@ -172,7 +172,7 @@ export default async function MyStay({
hotel.galleryImages[0]?.imageSizes.large hotel.galleryImages[0]?.imageSizes.large
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
const promoUrl = env.HIDE_FOR_NEXT_RELEASE const promoUrl = !env.isLangLive(params.lang)
? new URL(getCurrentWebUrl({ path: "/", lang })) ? new URL(getCurrentWebUrl({ path: "/", lang }))
: new URL(`${baseUrl}/${lang}/`) : new URL(`${baseUrl}/${lang}/`)

View File

@@ -12,8 +12,8 @@ export async function GET(
_request: NextRequest, _request: NextRequest,
context: { params: { sitemapId: string } } context: { params: { sitemapId: string } }
) { ) {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS !== "ALL_LANGUAGES_LIVE") {
notFound() return notFound()
} }
const sitemapId = context.params.sitemapId const sitemapId = context.params.sitemapId
@@ -21,12 +21,12 @@ export async function GET(
console.log("[SITEMAP] Fetching sitemap by ID", sitemapId) console.log("[SITEMAP] Fetching sitemap by ID", sitemapId)
if (!sitemapId) { if (!sitemapId) {
notFound() return notFound()
} }
const sitemapData = await getSitemapDataById(Number(sitemapId)) const sitemapData = await getSitemapDataById(Number(sitemapId))
if (!sitemapData.length) { if (!sitemapData.length) {
notFound() return notFound()
} }
const entries = sitemapData.map((entry) => { const entries = sitemapData.map((entry) => {

View File

@@ -7,8 +7,8 @@ import { getLastUpdated, getSitemapIds } from "@/utils/sitemap"
export const dynamic = "force-dynamic" export const dynamic = "force-dynamic"
export async function GET() { export async function GET() {
if (env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS !== "ALL_LANGUAGES_LIVE") {
notFound() return notFound()
} }
console.log(`[SITEMAP] Fetching sitemap`) console.log(`[SITEMAP] Fetching sitemap`)

View File

@@ -15,7 +15,7 @@ export default async function EmptyUpcomingStaysBlock() {
const lang = getLang() const lang = getLang()
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
const href = env.HIDE_FOR_NEXT_RELEASE const href = !env.isLangLive(lang)
? getCurrentWebUrl({ path: "/", lang, baseUrl }) ? getCurrentWebUrl({ path: "/", lang, baseUrl })
: `/${lang}` : `/${lang}`

View File

@@ -15,7 +15,7 @@ export default async function EmptyUpcomingStaysBlock() {
const lang = getLang() const lang = getLang()
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
const href = env.HIDE_FOR_NEXT_RELEASE const href = !env.isLangLive(lang)
? getCurrentWebUrl({ path: "/", lang, baseUrl }) ? getCurrentWebUrl({ path: "/", lang, baseUrl })
: `/${lang}` : `/${lang}`

View File

@@ -41,7 +41,7 @@ function DynamicContentBlocks(props: DynamicContentProps) {
case DynamicContentEnum.Blocks.components.earn_and_burn: case DynamicContentEnum.Blocks.components.earn_and_burn:
return <EarnAndBurn {...dynamic_content} /> return <EarnAndBurn {...dynamic_content} />
case DynamicContentEnum.Blocks.components.expiring_points: case DynamicContentEnum.Blocks.components.expiring_points:
return env.HIDE_FOR_NEXT_RELEASE ? null : ( return env.NEW_SITE_LIVE_STATUS === "NOT_LIVE" ? null : (
<ExpiringPoints {...dynamic_content} /> <ExpiringPoints {...dynamic_content} />
) )
case DynamicContentEnum.Blocks.components.how_it_works: case DynamicContentEnum.Blocks.components.how_it_works:

View File

@@ -1,13 +1,23 @@
import { Suspense } from "react"
import { import {
getPageSettingsBookingCode, getPageSettingsBookingCode,
isBookingWidgetHidden, isBookingWidgetHidden,
} from "@/lib/trpc/memoizedRequests" } from "@/lib/trpc/memoizedRequests"
import BookingWidgetClient from "./Client" import BookingWidgetClient, { BookingWidgetSkeleton } from "./Client"
import type { BookingWidgetProps } from "@/types/components/bookingWidget" import type { BookingWidgetProps } from "@/types/components/bookingWidget"
export default async function BookingWidget({ export async function BookingWidget(props: BookingWidgetProps) {
return (
<Suspense fallback={<BookingWidgetSkeleton />}>
<InternalBookingWidget {...props} />
</Suspense>
)
}
async function InternalBookingWidget({
type, type,
bookingWidgetSearchParams, bookingWidgetSearchParams,
}: BookingWidgetProps) { }: BookingWidgetProps) {

View File

@@ -2,6 +2,7 @@ import { env } from "@/env/server"
import { getFooter } from "@/lib/trpc/memoizedRequests" import { getFooter } from "@/lib/trpc/memoizedRequests"
import CurrentFooter from "@/components/Current/Footer" import CurrentFooter from "@/components/Current/Footer"
import { getLang } from "@/i18n/serverContext"
import FooterDetails from "./Details" import FooterDetails from "./Details"
import FooterNavigation from "./Navigation" import FooterNavigation from "./Navigation"
@@ -13,7 +14,8 @@ export function preload() {
} }
export default function Footer() { export default function Footer() {
if (env.HIDE_FOR_NEXT_RELEASE) { const lang = getLang()
if (!env.isLangLive(lang)) {
return <CurrentFooter /> return <CurrentFooter />
} }

View File

@@ -16,6 +16,7 @@ import useDropdownStore from "@/stores/main-menu"
import { IconName } from "@/components/Icons/iconName" import { IconName } from "@/components/Icons/iconName"
import LanguageSwitcher from "@/components/LanguageSwitcher" import LanguageSwitcher from "@/components/LanguageSwitcher"
import { useHandleKeyUp } from "@/hooks/useHandleKeyUp" import { useHandleKeyUp } from "@/hooks/useHandleKeyUp"
import { useIsLangLive } from "@/hooks/useIsLangLive"
import useLang from "@/hooks/useLang" import useLang from "@/hooks/useLang"
import { getCurrentWebUrl } from "@/utils/url" import { getCurrentWebUrl } from "@/utils/url"
@@ -32,6 +33,7 @@ export default function MobileMenu({
topLink, topLink,
isLoggedIn, isLoggedIn,
}: React.PropsWithChildren<MobileMenuProps>) { }: React.PropsWithChildren<MobileMenuProps>) {
const isLangLive = useIsLangLive()
const lang = useLang() const lang = useLang()
const intl = useIntl() const intl = useIntl()
const { const {
@@ -76,7 +78,7 @@ export default function MobileMenu({
}) })
const baseUrl = env.NEXT_PUBLIC_PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.NEXT_PUBLIC_PUBLIC_URL || "https://www.scandichotels.com"
const findMyBookingUrl = env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE const findMyBookingUrl = !isLangLive
? getCurrentWebUrl({ ? getCurrentWebUrl({
path: findMyBookingCurrentWebPath[lang], path: findMyBookingCurrentWebPath[lang],
lang, lang,

View File

@@ -34,7 +34,7 @@ export default async function TopMenu() {
const lang = getLang() const lang = getLang()
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
const findMyBookingUrl = env.HIDE_FOR_NEXT_RELEASE const findMyBookingUrl = !env.isLangLive(lang)
? getCurrentWebUrl({ ? getCurrentWebUrl({
path: findMyBookingCurrentWebPath[lang], path: findMyBookingCurrentWebPath[lang],
lang, lang,

View File

@@ -4,6 +4,7 @@ import { env } from "@/env/server"
import { getHeader, getName } from "@/lib/trpc/memoizedRequests" import { getHeader, getName } from "@/lib/trpc/memoizedRequests"
import CurrentHeader from "@/components/Current/Header" import CurrentHeader from "@/components/Current/Header"
import { getLang } from "@/i18n/serverContext"
import HeaderFallback from "../Current/Header/HeaderFallback" import HeaderFallback from "../Current/Header/HeaderFallback"
import MainMenu from "./MainMenu" import MainMenu from "./MainMenu"
@@ -14,7 +15,8 @@ import styles from "./header.module.css"
export default function Header() { export default function Header() {
void getName() void getName()
if (env.HIDE_FOR_NEXT_RELEASE) { const lang = getLang()
if (!env.isLangLive(lang)) {
return ( return (
<Suspense fallback={<HeaderFallback />}> <Suspense fallback={<HeaderFallback />}>
<CurrentHeader /> <CurrentHeader />

View File

@@ -28,9 +28,6 @@ export default function LanguageSwitcherContent({
const urlKeys = Object.keys(urls) as Lang[] const urlKeys = Object.keys(urls) as Lang[]
const pathname = usePathname() const pathname = usePathname()
const relValue = env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE
? "nofollow"
: undefined
return ( return (
<div className={styles.languageWrapper}> <div className={styles.languageWrapper}>
@@ -43,13 +40,14 @@ export default function LanguageSwitcherContent({
{urlKeys.map((key) => { {urlKeys.map((key) => {
const url = urls[key]?.url const url = urls[key]?.url
const isActive = currentLanguage === key const isActive = currentLanguage === key
if (url) { if (url) {
return ( return (
<li key={key}> <li key={key}>
<Link <Link
className={`${styles.link} ${isActive ? styles.active : ""}`} className={`${styles.link} ${isActive ? styles.active : ""}`}
href={replaceUrlPart(pathname, url)} href={replaceUrlPart(pathname, url)}
rel={relValue} rel={env.isLangLive(key) ? undefined : "nofollow"}
onClick={onLanguageSwitch} onClick={onLanguageSwitch}
keepSearchParams keepSearchParams
> >

View File

@@ -1,18 +1,24 @@
import { createEnv } from "@t3-oss/env-nextjs" import { createEnv } from "@t3-oss/env-nextjs"
import { z } from "zod" import { z } from "zod"
export const env = createEnv({ import { getLiveStatus } from "./getLiveStatus"
import { isLangLive } from "./isLangLive"
import type { Lang } from "@/constants/languages"
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_ENVIRONMENT: z.string().default("development"),
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001), NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001),
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE: z NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS: z
.string() .string()
// only allow "true" or "false" .regex(/^([a-z]{2},)*([a-z]{2}){0,1}$/)
.refine((s) => s === "true" || s === "false") .transform((val) => {
// transform to boolean return val.split(",")
.transform((s) => s === "true"), })
.default(""),
NEXT_PUBLIC_PUBLIC_URL: z.string().optional(), NEXT_PUBLIC_PUBLIC_URL: z.string().optional(),
}, },
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
@@ -22,8 +28,15 @@ export const env = createEnv({
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT, NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE:
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE, process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE: NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS:
process.env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE, process.env.NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS,
NEXT_PUBLIC_PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL, NEXT_PUBLIC_PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL,
}, },
}) })
export const env = {
..._env,
NEW_SITE_LIVE_STATUS: getLiveStatus(_env),
isLangLive: (lang: Lang) =>
isLangLive(lang, _env.NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS),
}

54
apps/scandic-web/env/getLiveStatus.ts vendored Normal file
View File

@@ -0,0 +1,54 @@
// Cannot use from "@/constants/languages" due to jiti
import { Lang } from "../constants/languages"
export function getLiveStatus(
internalEnv:
| {
NEW_SITE_LIVE_FOR_LANGS: string[]
}
| {
NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS: string[]
}
): "ALL_LANGUAGES_LIVE" | "SOME_LANGUAGES_LIVE" | "NOT_LIVE" {
const configuredLangs = getConfiguredLangs(internalEnv)
if (!configuredLangs) {
return "NOT_LIVE"
}
const allLangs = Object.keys(Lang)
const liveLangs = allLangs.filter((lang) => configuredLangs.includes(lang))
if (liveLangs.length === 0) {
return "NOT_LIVE"
}
if (
liveLangs.length === allLangs.length &&
allLangs.every((lang) => liveLangs.includes(lang))
) {
return "ALL_LANGUAGES_LIVE"
}
return "SOME_LANGUAGES_LIVE"
}
function getConfiguredLangs<T extends {}>(env: T): string[] | undefined {
if (
!(
"NEW_SITE_LIVE_FOR_LANGS" in env ||
"NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS" in env
)
) {
return undefined
}
const configuredLangs =
"NEW_SITE_LIVE_FOR_LANGS" in env
? env.NEW_SITE_LIVE_FOR_LANGS
: env.NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS
if (!Array.isArray(configuredLangs)) {
throw new Error("Misconfigured environment variable, expected array")
}
return configuredLangs
}

18
apps/scandic-web/env/isLangLive.test.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
import { describe, expect, it } from "@jest/globals"
import { Lang } from "@/constants/languages"
import { isLangLive } from "./isLangLive"
describe("hideForNextRelease", () => {
it("should return true if en is part of live languages", () => {
expect(isLangLive(Lang.en, ["en", "sv"])).toBe(true)
expect(isLangLive(Lang.en, ["en"])).toBe(true)
})
it("should return false if en is not part of live languages", () => {
expect(isLangLive(Lang.en, [])).toBe(false)
expect(isLangLive(Lang.en, ["sv"])).toBe(false)
expect(isLangLive(Lang.en, ["sv,fi"])).toBe(false)
})
})

5
apps/scandic-web/env/isLangLive.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
import type { Lang } from "@/constants/languages"
export function isLangLive(lang: Lang, liveLangs: string[]): boolean {
return liveLangs.includes(lang)
}

View File

@@ -1,9 +1,14 @@
import { createEnv } from "@t3-oss/env-nextjs" import { createEnv } from "@t3-oss/env-nextjs"
import { z } from "zod" import { z } from "zod"
import { getLiveStatus } from "./getLiveStatus"
import { isLangLive } from "./isLangLive"
import type { Lang } from "../constants/languages"
const TWENTYFOUR_HOURS = 24 * 60 * 60 const TWENTYFOUR_HOURS = 24 * 60 * 60
export const env = createEnv({ const _env = createEnv({
/** /**
* Due to t3-env only checking typeof window === "undefined" * Due to t3-env only checking typeof window === "undefined"
* and Netlify running Deno, window is never "undefined" * and Netlify running Deno, window is never "undefined"
@@ -107,19 +112,6 @@ export const env = createEnv({
GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string(), GOOGLE_STATIC_MAP_SIGNATURE_SECRET: z.string(),
GOOGLE_DYNAMIC_MAP_ID: z.string(), GOOGLE_DYNAMIC_MAP_ID: z.string(),
GOOGLE_STATIC_MAP_ID: z.string(), GOOGLE_STATIC_MAP_ID: z.string(),
HIDE_FOR_NEXT_RELEASE: z
.string()
// only allow "true" or "false"
.refine((s) => s === "true" || s === "false")
// transform to boolean
.transform((s) => s === "true"),
ENABLE_BOOKING_FLOW: z
.string()
// only allow "true" or "false"
.refine((s) => s === "true" || s === "false")
// transform to boolean
.transform((s) => s === "true")
.default("true"),
ENABLE_BOOKING_WIDGET: z ENABLE_BOOKING_WIDGET: z
.string() .string()
// only allow "true" or "false" // only allow "true" or "false"
@@ -127,13 +119,6 @@ export const env = createEnv({
// transform to boolean // transform to boolean
.transform((s) => s === "true") .transform((s) => s === "true")
.default("false"), .default("false"),
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH: z
.string()
// only allow "true" or "false"
.refine((s) => s === "true" || s === "false")
// transform to boolean
.transform((s) => s === "true")
.default("true"),
ENABLE_SURPRISES: z ENABLE_SURPRISES: z
.string() .string()
// only allow "true" or "false" // only allow "true" or "false"
@@ -200,6 +185,17 @@ export const env = createEnv({
.transform((s) => s === "true") .transform((s) => s === "true")
.default("true"), .default("true"),
WARMUP_TOKEN: z.string().optional(), WARMUP_TOKEN: z.string().optional(),
/**
* Include the languages that should be hidden for the next release
* Should be in the format of "en,da,de,fi,no,sv" or empty
*/
NEW_SITE_LIVE_FOR_LANGS: z
.string()
.regex(/^([a-z]{2},)*([a-z]{2}){0,1}$/)
.transform((val) => {
return val.split(",")
})
.default(""),
}, },
emptyStringAsUndefined: true, emptyStringAsUndefined: true,
runtimeEnv: { runtimeEnv: {
@@ -268,12 +264,8 @@ export const env = createEnv({
process.env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET, process.env.GOOGLE_STATIC_MAP_SIGNATURE_SECRET,
GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID, GOOGLE_STATIC_MAP_ID: process.env.GOOGLE_STATIC_MAP_ID,
GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID, GOOGLE_DYNAMIC_MAP_ID: process.env.GOOGLE_DYNAMIC_MAP_ID,
HIDE_FOR_NEXT_RELEASE: process.env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE,
USE_NEW_REWARD_MODEL: process.env.USE_NEW_REWARD_MODEL, USE_NEW_REWARD_MODEL: process.env.USE_NEW_REWARD_MODEL,
ENABLE_BOOKING_FLOW: process.env.ENABLE_BOOKING_FLOW,
ENABLE_BOOKING_WIDGET: process.env.ENABLE_BOOKING_WIDGET, ENABLE_BOOKING_WIDGET: process.env.ENABLE_BOOKING_WIDGET,
ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH:
process.env.ENABLE_BOOKING_WIDGET_HOTELRESERVATION_PATH,
ENABLE_SURPRISES: process.env.ENABLE_SURPRISES, ENABLE_SURPRISES: process.env.ENABLE_SURPRISES,
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_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
@@ -295,9 +287,16 @@ export const env = createEnv({
GIT_SHA: process.env.GIT_SHA, GIT_SHA: process.env.GIT_SHA,
ENABLE_WARMUP_HOTEL: process.env.ENABLE_WARMUP_HOTEL, ENABLE_WARMUP_HOTEL: process.env.ENABLE_WARMUP_HOTEL,
WARMUP_TOKEN: process.env.WARMUP_TOKEN, WARMUP_TOKEN: process.env.WARMUP_TOKEN,
NEW_SITE_LIVE_FOR_LANGS: process.env.NEXT_PUBLIC_NEW_SITE_LIVE_FOR_LANGS,
}, },
}) })
export const env = {
..._env,
NEW_SITE_LIVE_STATUS: getLiveStatus(_env),
isLangLive: (lang: Lang) => isLangLive(lang, _env.NEW_SITE_LIVE_FOR_LANGS),
} as const
function replaceTopLevelDomain(url: string, domain: string) { function replaceTopLevelDomain(url: string, domain: string) {
return url.replaceAll("{topleveldomain}", domain) return url.replaceAll("{topleveldomain}", domain)
} }

View File

@@ -0,0 +1,10 @@
"use client"
import { env } from "@/env/client"
import useLang from "./useLang"
export function useIsLangLive(): boolean {
const lang = useLang()
return env.isLangLive(lang)
}

View File

@@ -2,7 +2,6 @@
import { useCallback, useEffect, useState } from "react" import { useCallback, useEffect, useState } from "react"
import { env } from "@/env/client"
import useStickyPositionStore, { import useStickyPositionStore, {
type StickyElement, type StickyElement,
type StickyElementNameEnum, type StickyElementNameEnum,
@@ -10,6 +9,8 @@ import useStickyPositionStore, {
import { debounce } from "@/utils/debounce" import { debounce } from "@/utils/debounce"
import { useIsLangLive } from "./useIsLangLive"
interface UseStickyPositionProps { interface UseStickyPositionProps {
ref?: React.RefObject<HTMLElement> ref?: React.RefObject<HTMLElement>
name?: StickyElementNameEnum name?: StickyElementNameEnum
@@ -46,6 +47,7 @@ export default function useStickyPosition({
getAllElements, getAllElements,
} = useStickyPositionStore() } = useStickyPositionStore()
const isLangLive = useIsLangLive()
/* Used for Current mobile header since that doesn't use this hook. /* Used for Current mobile header since that doesn't use this hook.
* *
* Instead, calculate if the mobile header is shown and add the height of * Instead, calculate if the mobile header is shown and add the height of
@@ -123,7 +125,7 @@ export default function useStickyPosition({
updateHeights() updateHeights()
// Only do this special handling if we have the current header // Only do this special handling if we have the current header
if (env.NEXT_PUBLIC_HIDE_FOR_NEXT_RELEASE) { if (!isLangLive) {
if (document.body.clientWidth > 950) { if (document.body.clientWidth > 950) {
setBaseTopOffset(0) setBaseTopOffset(0)
} else { } else {
@@ -142,7 +144,7 @@ export default function useStickyPosition({
resizeObserver.unobserve(document.body) resizeObserver.unobserve(document.body)
} }
} }
}, [updateHeights]) }, [updateHeights, isLangLive])
return { return {
currentHeight: ref?.current?.offsetHeight || null, currentHeight: ref?.current?.offsetHeight || null,

View File

@@ -1,7 +1,5 @@
import { type NextMiddleware, NextResponse } from "next/server" import { type NextMiddleware, NextResponse } from "next/server"
import { notFound } from "@/server/errors/next"
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath" import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"
import { findLang } from "@/utils/languages" import { findLang } from "@/utils/languages"
import { removeTrailingSlash } from "@/utils/url" import { removeTrailingSlash } from "@/utils/url"
@@ -29,7 +27,7 @@ export const middleware: NextMiddleware = async (request) => {
if (!contentType || !uid) { if (!contentType || !uid) {
// Routes to subpages we need to check if the parent of the incomingPathName exists. // Routes to subpages we need to check if the parent of the incomingPathName exists.
// Then we considered the incomingPathName to be a subpage. These subpages do not live in the CMS. // Then we considered the incomingPathName to be a subpage. These subpages do NOT_LIVE in the CMS.
const incomingPathNameParts = incomingPathName.split("/") const incomingPathNameParts = incomingPathName.split("/")
// If the incomingPathName has 2 or more parts, it could possibly be a subpage. // If the incomingPathName has 2 or more parts, it could possibly be a subpage.

View File

@@ -230,7 +230,7 @@ export const metadataQueryRouter = router({
if (metadata) { if (metadata) {
if (alternates) { if (alternates) {
// Hiding alternates until all languages are released in production // Hiding alternates until all languages are released in production
if (!env.HIDE_FOR_NEXT_RELEASE) { if (env.NEW_SITE_LIVE_STATUS === "ALL_LANGUAGES_LIVE") {
metadata.alternates = alternates metadata.alternates = alternates
} }
} }

View File

@@ -334,7 +334,7 @@ export async function updateStaysBookingUrl(
const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com" const baseUrl = env.PUBLIC_URL || "https://www.scandichotels.com"
// Construct Booking URL. // Construct Booking URL.
const bookingUrl = env.HIDE_FOR_NEXT_RELEASE const bookingUrl = !env.isLangLive(lang)
? new URL( ? new URL(
getCurrentWebUrl({ getCurrentWebUrl({
path: myBookingPath[lang], path: myBookingPath[lang],

View File

@@ -11,9 +11,8 @@ export async function getServiceToken() {
const tracer = trace.getTracer("getServiceToken") const tracer = trace.getTracer("getServiceToken")
return await tracer.startActiveSpan("getServiceToken", async () => { return await tracer.startActiveSpan("getServiceToken", async () => {
const scopes = env.ENABLE_BOOKING_FLOW const scopes = ["profile", "hotel", "booking", "package", "availability"]
? ["profile", "hotel", "booking", "package", "availability"]
: ["profile"]
const cacheKey = getServiceTokenCacheKey(scopes) const cacheKey = getServiceTokenCacheKey(scopes)
const cacheClient = await getCacheClient() const cacheClient = await getCacheClient()
const token = await getOrSetServiceTokenFromCache(cacheKey, scopes, tracer) const token = await getOrSetServiceTokenFromCache(cacheKey, scopes, tracer)