diff --git a/apps/scandic-web/app/[lang]/(live)/(protected)/logoutSafely/route.ts b/apps/scandic-web/app/[lang]/(live)/(protected)/logoutSafely/route.ts new file mode 100644 index 000000000..6eb8ba863 --- /dev/null +++ b/apps/scandic-web/app/[lang]/(live)/(protected)/logoutSafely/route.ts @@ -0,0 +1,44 @@ +import { type NextRequest,NextResponse } from "next/server" +import { AuthError } from "next-auth" + +import { logger } from "@scandic-hotels/common/logger" + +import { env } from "@/env/server" +import { internalServerError } from "@/server/errors/next" +import { getPublicURL } from "@/server/utils" + +import { signOut } from "@/auth" + +export async function GET(request: NextRequest) { + const publicURL = getPublicURL(request) + const redirectToSearchParamValue = + request.nextUrl.searchParams.get("redirectTo") + const redirectToFallback = "/" + + let redirectTo: string = redirectToSearchParamValue || redirectToFallback + + // Make relative URL to absolute URL + if (redirectTo.startsWith("/")) { + redirectTo = new URL(redirectTo, publicURL).href + } + + try { + redirectTo = `${env.CURITY_ISSUER_USER}/authn/authenticate/logout?redirect_uri=${encodeURIComponent(redirectTo)}` + logger.debug(`[logoutSafely] final redirectUrl: ${redirectTo}`) + + const redirectUrlObj = await signOut({ + redirectTo, + redirect: false, + }) + + return NextResponse.redirect(redirectUrlObj.redirect) + } catch (error) { + if (error instanceof AuthError) { + logger.error("signOutSafelyAuthError", { signOutAuthError: error }) + } else { + logger.error("signOutSafelyError", { signOutError: error }) + } + } + + return internalServerError() +} diff --git a/apps/scandic-web/app/[lang]/(live)/error/user-not-found/page.tsx b/apps/scandic-web/app/[lang]/(live)/error/user-not-found/page.tsx new file mode 100644 index 000000000..42a99942a --- /dev/null +++ b/apps/scandic-web/app/[lang]/(live)/error/user-not-found/page.tsx @@ -0,0 +1,14 @@ +import { UserNotFound } from "@/components/UserNotFound/UserNotFound" + +import type { Metadata } from "next" + +export const metadata: Metadata = { + robots: { + index: false, + follow: false, + }, +} + +export default function UserNotFoundPage() { + return +} diff --git a/apps/scandic-web/app/[lang]/(live)/layout.tsx b/apps/scandic-web/app/[lang]/(live)/layout.tsx index 4f7c83769..197b6565d 100644 --- a/apps/scandic-web/app/[lang]/(live)/layout.tsx +++ b/apps/scandic-web/app/[lang]/(live)/layout.tsx @@ -24,6 +24,7 @@ import SitewideAlert from "@/components/SitewideAlert" import { ToastHandler } from "@/components/TempDesignSystem/Toasts" import AdobeSDKScript from "@/components/TrackingSDK/AdobeSDKScript" import GTMScript from "@/components/TrackingSDK/GTMScript" +import { UserExists } from "@/components/UserExists" import { FontPreload } from "@/fonts/font-preloading" import { getMessages } from "@/i18n" import ClientIntlProvider from "@/i18n/Provider" @@ -87,6 +88,7 @@ export default async function RootLayout( + diff --git a/apps/scandic-web/components/UserExists.tsx b/apps/scandic-web/components/UserExists.tsx new file mode 100644 index 000000000..f8780d7fd --- /dev/null +++ b/apps/scandic-web/components/UserExists.tsx @@ -0,0 +1,43 @@ +"use client" + +import { redirect } from "next/navigation" +import { useSession } from "next-auth/react" + +import { trpc } from "@scandic-hotels/trpc/client" + +import { userNotFound } from "@/constants/routes/errorPages" +import { logoutSafely } from "@/constants/routes/handleAuth" + +import useLang from "@/hooks/useLang" +import { isValidClientSession } from "@/utils/clientSession" + +export function UserExists() { + const { data: session } = useSession() + const isUserLoggedIn = isValidClientSession(session) + const lang = useLang() + + const { data, isLoading: isLoadingUser } = trpc.user.get.useQuery(undefined, { + enabled: isUserLoggedIn, + }) + + if (!isUserLoggedIn) { + return null + } + + if (isLoadingUser) { + return null + } + + if (data && "error" in data && data.error) { + switch (data.cause) { + case "notfound": + redirect( + `${logoutSafely[lang]}?redirectTo=${encodeURIComponent(userNotFound[lang])}` + ) + default: + break + } + } + + return null +} diff --git a/apps/scandic-web/components/UserNotFound/UserNotFound.module.css b/apps/scandic-web/components/UserNotFound/UserNotFound.module.css new file mode 100644 index 000000000..2131b017b --- /dev/null +++ b/apps/scandic-web/components/UserNotFound/UserNotFound.module.css @@ -0,0 +1,65 @@ +.container { + margin-top: 0; + background: var(--Main-Grey-White); + position: relative; + border-top: 50px solid var(--Main-Grey-White); + background-clip: content-box; +} + +.content { + position: relative; + text-align: center; + box-sizing: content-box; + max-width: var(--max-width-page); + margin: 0 auto; + padding: 70px 30px; +} + +.header { + font-family: + brandon text, + Arial, + Helvetica, + sans-serif; + font-size: 32px; + line-height: 1; + margin: 0; + text-transform: uppercase; + font-weight: 400; + color: #483729; +} + +.pitch { + margin-top: 32px; + margin-bottom: 16px; +} + +.text { + font-family: + Helvetica Neue, + Roboto, + Helvetica, + Arial, + sans-serif; + font-weight: 300; + line-height: normal; + text-transform: none; + font-size: 20px; + color: #333; +} + +@media screen and (min-width: 740px) { + .content { + text-align: left; + } +} + +@media screen and (min-width: 950px) { + .header { + font-size: 46px; + } + + .text { + font-size: 24px; + } +} diff --git a/apps/scandic-web/components/UserNotFound/UserNotFound.tsx b/apps/scandic-web/components/UserNotFound/UserNotFound.tsx new file mode 100644 index 000000000..d2ece9de4 --- /dev/null +++ b/apps/scandic-web/components/UserNotFound/UserNotFound.tsx @@ -0,0 +1,28 @@ +"use client" + +import { useIntl } from "react-intl" + +import styles from "./UserNotFound.module.css" + +export function UserNotFound() { + const intl = useIntl() + + return ( +
+
+

+ {intl.formatMessage({ + defaultMessage: "User not found", + })} +

+
+

+ {intl.formatMessage({ + defaultMessage: "Please try again or contact customer service!", + })} +

+
+
+
+ ) +} diff --git a/apps/scandic-web/constants/routes/errorPages.ts b/apps/scandic-web/constants/routes/errorPages.ts new file mode 100644 index 000000000..6ec881849 --- /dev/null +++ b/apps/scandic-web/constants/routes/errorPages.ts @@ -0,0 +1,10 @@ +import type { LangRoute } from "@scandic-hotels/common/constants/routes/langRoute" + +export const userNotFound: LangRoute = { + en: "/en/error/user-not-found", + sv: "/sv/error/user-not-found", + no: "/no/error/user-not-found", + fi: "/fi/error/user-not-found", + da: "/da/error/user-not-found", + de: "/de/error/user-not-found", +} diff --git a/apps/scandic-web/constants/routes/handleAuth.js b/apps/scandic-web/constants/routes/handleAuth.js index 507b481a5..faedc816e 100644 --- a/apps/scandic-web/constants/routes/handleAuth.js +++ b/apps/scandic-web/constants/routes/handleAuth.js @@ -32,6 +32,15 @@ export const logout = { sv: "/sv/logga-ut", } +export const logoutSafely = { + da: "/da/logoutSafely", + de: "/de/logoutSafely", + en: "/en/logoutSafely", + fi: "/fi/logoutSafely", + no: "/no/logoutSafely", + sv: "/sv/logoutSafely", +} + /** @type {import('@scandic-hotels/common/constants/routes/langRoute').LangRoute} */ export const logoutUnLocalized = { da: "/da/logout", @@ -58,4 +67,5 @@ export const handleAuth = [ ...Object.values(verifymagiclink), ...Object.values(loginUnLocalized), ...Object.values(logoutUnLocalized), + ...Object.values(logoutSafely), ] diff --git a/apps/scandic-web/middleware.ts b/apps/scandic-web/middleware.ts index f7d915bc6..be8930342 100644 --- a/apps/scandic-web/middleware.ts +++ b/apps/scandic-web/middleware.ts @@ -12,6 +12,7 @@ import * as currentWebLogin from "@/middlewares/currentWebLogin" import * as currentWebLoginEmail from "@/middlewares/currentWebLoginEmail" import * as currentWebLogout from "@/middlewares/currentWebLogout" import * as dateQueryParams from "@/middlewares/dateQueryParams" +import * as errorPages from "@/middlewares/errorPages" import * as familyAndFriends from "@/middlewares/familyAndFriends" import * as handleAuth from "@/middlewares/handleAuth" import * as handleDTMC from "@/middlewares/handleDTMC" @@ -67,6 +68,7 @@ export const middleware: NextMiddleware = async (request, event) => { dateQueryParams, legacySearchParams, bookingFlow, + errorPages, familyAndFriends, sasXScandic, cmsContent, diff --git a/apps/scandic-web/middlewares/errorPages.ts b/apps/scandic-web/middlewares/errorPages.ts new file mode 100644 index 000000000..49044f0c9 --- /dev/null +++ b/apps/scandic-web/middlewares/errorPages.ts @@ -0,0 +1,20 @@ +import { type NextMiddleware, NextResponse } from "next/server" + +import { getDefaultRequestHeaders } from "./utils" + +import type { MiddlewareMatcher } from "@/types/middleware" + +export const middleware: NextMiddleware = async (request) => { + const headers = getDefaultRequestHeaders(request) + headers.set("X-Robots-Tag", "noindex, nofollow") + + return NextResponse.next({ + request: { + headers, + }, + }) +} + +export const matcher: MiddlewareMatcher = (request) => { + return !!request.nextUrl.pathname.match(/^\/(da|de|en|fi|no|sv)\/(error)/) +}