diff --git a/components/Current/Header/TopMenu/index.tsx b/components/Current/Header/TopMenu/index.tsx index 69d23798e..be0e99d97 100644 --- a/components/Current/Header/TopMenu/index.tsx +++ b/components/Current/Header/TopMenu/index.tsx @@ -1,7 +1,6 @@ import { logout } from "@/constants/routes/handleAuth" import { serverClient } from "@/lib/trpc/server" -import { auth } from "@/auth" import Link from "@/components/TempDesignSystem/Link" import { getIntl } from "@/i18n" @@ -23,9 +22,7 @@ export default async function TopMenu({ lang, }: TopMenuProps) { const { formatMessage } = await getIntl() - const session = await auth() - const user = session ? await serverClient().user.get() : null - + const user = await serverClient().user.name() return (
@@ -46,7 +43,7 @@ export default async function TopMenu({ ))}
  • - {session ? ( + {user ? ( <> {user ? ( li::marker { color: var(--Primary-Light-On-Surface-Accent); } -.ul:has(.heart), -.ul:has(.check) { +.li:has(.heart), +.li:has(.check) { list-style: none; } + .li:has(.heart), .li:has(.check) { display: flex; @@ -54,7 +54,7 @@ } .container { - display: "grid"; + display: grid; gap: var(--Spacing-x3); max-width: 1197px; } diff --git a/components/JsonToHtml/renderOptions.tsx b/components/JsonToHtml/renderOptions.tsx index f585dc768..b3d0cef9a 100644 --- a/components/JsonToHtml/renderOptions.tsx +++ b/components/JsonToHtml/renderOptions.tsx @@ -497,7 +497,10 @@ export const renderOptions: RenderOptions = { className={styles.ul} style={ numberOfRows - ? { gridTemplateRows: `repeat(${numberOfRows}, auto)` } + ? { + gridTemplateRows: `repeat(${numberOfRows}, auto)`, + gridAutoFlow: "column", + } : {} } > diff --git a/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx index f174ee491..e1594d97f 100644 --- a/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/OverviewTable/index.tsx @@ -124,13 +124,15 @@ function reducer(state: any, action: OverviewTableReducerAction) { } } -export default function OverviewTable({ user }: OverviewTableProps) { +export default function OverviewTable({ + activeMembership, +}: OverviewTableProps) { const intl = useIntl() const lang = Lang.en const levelsData = levelsTranslations[lang] const [selectionState, dispatch] = useReducer( reducer, - { user, lang }, + { activeMembership, lang }, getInitialState ) @@ -150,10 +152,6 @@ export default function OverviewTable({ user }: OverviewTableProps) { value: level.level, })) - const activeMembership = user?.memberships - ? getMembership(user.memberships) - : null - let activeMembershipLevel: membershipLevels | null = null if (activeMembership?.membershipLevel) { activeMembershipLevel = membershipLevels[activeMembership?.membershipLevel] diff --git a/components/Loyalty/Blocks/DynamicContent/index.tsx b/components/Loyalty/Blocks/DynamicContent/index.tsx index 3fb13b2af..235fe9ecc 100644 --- a/components/Loyalty/Blocks/DynamicContent/index.tsx +++ b/components/Loyalty/Blocks/DynamicContent/index.tsx @@ -21,9 +21,7 @@ import type { import { LoyaltyComponentEnum } from "@/types/components/loyalty/enums" async function DynamicComponentBlock({ component }: DynamicComponentProps) { - const session = await auth() - - const user = session ? await serverClient().user.get() : null + const membershipLevel = await serverClient().user.membershipLevel() switch (component) { case LoyaltyComponentEnum.how_it_works: @@ -31,7 +29,7 @@ async function DynamicComponentBlock({ component }: DynamicComponentProps) { case LoyaltyComponentEnum.loyalty_levels: return case LoyaltyComponentEnum.overview_table: - return + return default: return null } diff --git a/components/Loyalty/Sidebar/JoinLoyalty/index.tsx b/components/Loyalty/Sidebar/JoinLoyalty/index.tsx index e940aab0f..0a49a80e3 100644 --- a/components/Loyalty/Sidebar/JoinLoyalty/index.tsx +++ b/components/Loyalty/Sidebar/JoinLoyalty/index.tsx @@ -1,6 +1,5 @@ -import { login } from "@/constants/routes/handleAuth" +import { serverClient } from "@/lib/trpc/server" -import { auth } from "@/auth" import ArrowRight from "@/components/Icons/ArrowRight" import { ScandicFriends } from "@/components/Levels" import Button from "@/components/TempDesignSystem/Button" @@ -21,9 +20,10 @@ export default async function JoinLoyaltyContact({ lang, }: JoinLoyaltyContactProps & LangParams) { const { formatMessage } = await getIntl() - const session = await auth() + const user = await serverClient().user.name() - if (session) { + // Check if we have user, that means we are logged in. + if (user) { return null } return ( diff --git a/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx b/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx index 72bc034d3..587074062 100644 --- a/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx +++ b/components/Loyalty/Sidebar/MyPagesNavigation/index.tsx @@ -1,12 +1,14 @@ -import { auth } from "@/auth" +import { serverClient } from "@/lib/trpc/server" + import MyPagesSidebar from "@/components/MyPages/Sidebar" import { LangParams } from "@/types/params" export default async function MyPagesNavigation({ lang }: LangParams) { - const session = await auth() + const user = await serverClient().user.name() - if (!session) { + // Check if we have user, that means we are logged in. + if (!user) { return null } diff --git a/server/context.ts b/server/context.ts index 537719540..2c4c235ee 100644 --- a/server/context.ts +++ b/server/context.ts @@ -10,7 +10,7 @@ import { unauthorizedError } from "./errors/trpc" typeof auth type CreateContextOptions = { - auth: () => Promise + auth: () => Promise lang: Lang pathname: string uid?: string | null @@ -50,7 +50,7 @@ export function createContext() { const session = await auth() const webToken = webviewTokenCookie?.value if (!session?.token && !webToken) { - throw unauthorizedError() + return null } return session || ({ token: { access_token: webToken } } as Session) diff --git a/server/routers/user/query.ts b/server/routers/user/query.ts index c80a086c5..30a77d9c4 100644 --- a/server/routers/user/query.ts +++ b/server/routers/user/query.ts @@ -1,9 +1,13 @@ import * as api from "@/lib/api" -import { protectedProcedure, router } from "@/server/trpc" +import { + protectedProcedure, + router, + safeProtectedProcedure, +} from "@/server/trpc" import { countries } from "@/components/TempDesignSystem/Form/Country/countries" import * as maskValue from "@/utils/maskValue" -import { getMembershipCards } from "@/utils/user" +import { getMembership, getMembershipCards } from "@/utils/user" import { friendTransactionsInput, @@ -19,6 +23,37 @@ import { } from "./output" import { benefits, extendedUser, nextLevelPerks } from "./temp" +import type { Session } from "next-auth" + +async function getVerifiedUser({ session }: { session: Session }) { + const apiResponse = await api.get(api.endpoints.v1.profile, { + cache: "no-store", + headers: { + Authorization: `Bearer ${session.token.access_token}`, + }, + }) + + if (!apiResponse.ok) { + return null + } + + const apiJson = await apiResponse.json() + if (!apiJson.data?.attributes) { + console.error(`User has no data - (user: ${JSON.stringify(session.user)})`) + return null + } + + const verifiedData = getUserSchema.safeParse(apiJson.data.attributes) + if (!verifiedData.success) { + console.info( + `Failed to validate User - (User: ${JSON.stringify(session.user)})` + ) + console.error(verifiedData.error) + return null + } + return verifiedData +} + function fakingRequest(payload: T): Promise { return new Promise((resolve) => { setTimeout(() => { @@ -31,45 +66,9 @@ export const userQueryRouter = router({ get: protectedProcedure .input(getUserInputSchema) .query(async function getUser({ ctx, input }) { - const apiResponse = await api.get(api.endpoints.v1.profile, { - cache: "no-store", - headers: { - Authorization: `Bearer ${ctx.session.token.access_token}`, - }, - }) + const verifiedData = await getVerifiedUser({ session: ctx.session }) - if (!apiResponse.ok) { - // switch (apiResponse.status) { - // case 400: - // throw badRequestError() - // case 401: - // throw unauthorizedError() - // case 403: - // throw forbiddenError() - // default: - // throw internalServerError() - // } - console.info(`API Response Failed - Getting User`) - console.info(`User: (${JSON.stringify(ctx.session.user)})`) - console.error(apiResponse) - return null - } - - const apiJson = await apiResponse.json() - if (!apiJson.data?.attributes) { - // throw notFound(apiJson) - console.error( - `User has no data - (user: ${JSON.stringify(ctx.session.user)})` - ) - return null - } - - const verifiedData = getUserSchema.safeParse(apiJson.data.attributes) - if (!verifiedData.success) { - console.info( - `Failed to validate User - (User: ${JSON.stringify(ctx.session.user)})` - ) - console.error(verifiedData.error) + if (!verifiedData) { return null } @@ -119,7 +118,33 @@ export const userQueryRouter = router({ return user }), + name: safeProtectedProcedure.query(async function ({ ctx }) { + if (!ctx.session) { + return null + } + const verifiedData = await getVerifiedUser({ session: ctx.session }) + if (!verifiedData) { + return null + } + return { + firstName: verifiedData.data.firstName, + lastName: verifiedData.data.lastName, + } + }), + membershipLevel: safeProtectedProcedure.query(async function ({ ctx }) { + if (!ctx.session) { + return null + } + const verifiedData = await getVerifiedUser({ session: ctx.session }) + + if (!verifiedData) { + return null + } + + const membershipLevel = getMembership(verifiedData.data.memberships) + return membershipLevel + }), benefits: router({ current: protectedProcedure.query(async function (opts) { // TODO: Make request to get user data from Scandic API diff --git a/server/trpc.ts b/server/trpc.ts index 3f7cd3c70..1caf32b53 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -2,10 +2,16 @@ import { initTRPC } from "@trpc/server" import { env } from "@/env/server" -import { badRequestError, sessionExpiredError } from "./errors/trpc" +import { + badRequestError, + sessionExpiredError, + unauthorizedError, +} from "./errors/trpc" import { transformer } from "./transformer" import { langInput } from "./utils" +import type { Session } from "next-auth" + import type { Meta } from "@/types/trpc/meta" import type { Context } from "./context" @@ -57,6 +63,10 @@ export const protectedProcedure = t.procedure.use(async function (opts) { console.info(`path: ${opts.path} | type: ${opts.type}`) } + if (!session) { + throw unauthorizedError() + } + if (session?.error === "RefreshAccessTokenError") { throw sessionExpiredError() } @@ -67,3 +77,25 @@ export const protectedProcedure = t.procedure.use(async function (opts) { }, }) }) + +export const safeProtectedProcedure = t.procedure.use(async function (opts) { + const authRequired = opts.meta?.authRequired ?? true + + let session: Session | null = await opts.ctx.auth() + if (!authRequired && env.NODE_ENV === "development") { + console.info( + `❌❌❌❌ You are opting out of authorization, if its done on purpose maybe you should use the publicProcedure instead. ❌❌❌❌` + ) + console.info(`path: ${opts.path} | type: ${opts.type}`) + } + + if (!session || session.error === "RefreshAccessTokenError") { + session = null + } + + return opts.next({ + ctx: { + session, + }, + }) +}) diff --git a/types/components/current/header/mainMenu.ts b/types/components/current/header/mainMenu.ts index 4251fd529..8e468e8cc 100644 --- a/types/components/current/header/mainMenu.ts +++ b/types/components/current/header/mainMenu.ts @@ -16,6 +16,6 @@ export type MainMenuProps = { languageSwitcher: React.ReactNode | null myPagesMobileDropdown: React.ReactNode | null bookingHref: string - user: User | null + user: Pick | null lang: Lang } diff --git a/types/components/loyalty/blocks.ts b/types/components/loyalty/blocks.ts index f9f337344..c8db57354 100644 --- a/types/components/loyalty/blocks.ts +++ b/types/components/loyalty/blocks.ts @@ -7,9 +7,9 @@ import { RteBlockContent, } from "@/server/routers/contentstack/loyaltyPage/output" -import type { IntlFormatters } from "@formatjs/intl" +import { MembershipLevel } from "@/utils/user" -import { User } from "@/types/user" +import type { IntlFormatters } from "@formatjs/intl" export type BlocksProps = { blocks: Block[] @@ -32,7 +32,7 @@ export type Content = { content: RteBlockContent["content"]["content"] } type Benefit = { title: string } -export type OverviewTableProps = { user: User | null } +export type OverviewTableProps = { activeMembership: MembershipLevel | null } export type Level = { level: membershipLevels diff --git a/utils/user.ts b/utils/user.ts index 052826eaf..57f4e3e16 100644 --- a/utils/user.ts +++ b/utils/user.ts @@ -15,6 +15,7 @@ export function getMembership(memberships: User["memberships"]) { membership.membershipType.toLowerCase() === scandicMemberships.guestpr ) } +export type MembershipLevel = ReturnType export function getMembershipCards( memberships: z.infer