Merged in feat/SW-1488-unlink-sas-account (pull request #1349)

Implement unlink SAS flow

Approved-by: Joakim Jäderberg
This commit is contained in:
Anton Gunnarsson
2025-02-20 15:09:06 +00:00
parent fc720b1dbc
commit 340f6d1714
22 changed files with 216 additions and 82 deletions

View File

@@ -78,7 +78,10 @@ export async function GET(
httpOnly: true, httpOnly: true,
}) })
if (stateResult.data.intent === "link") { if (
stateResult.data.intent === "link" ||
stateResult.data.intent === "unlink"
) {
const [data, error] = await safeTry( const [data, error] = await safeTry(
serverClient().partner.sas.requestOtp({}) serverClient().partner.sas.requestOtp({})
) )

View File

@@ -29,7 +29,7 @@ export default async function SasXScandicLayout({
<Link className={styles.backLink} href={profileOverview[params.lang]}> <Link className={styles.backLink} href={profileOverview[params.lang]}>
<ArrowLeft height={20} width={20} /> <ArrowLeft height={20} width={20} />
<span className={styles.long}> <span className={styles.long}>
{intl.formatMessage({ id: "Back to Scandichotels.com" })} {intl.formatMessage({ id: "Back to scandichotels.com" })}
</span> </span>
<span className={styles.short}> <span className={styles.short}>
{intl.formatMessage({ id: "Back" })} {intl.formatMessage({ id: "Back" })}

View File

@@ -18,8 +18,11 @@ import type { LangParams, PageArgs, SearchParams } from "@/types/params"
import type { State } from "../sasUtils" import type { State } from "../sasUtils"
const searchParamsSchema = z.object({ const searchParamsSchema = z.object({
intent: z.enum(["link"]), intent: z.enum(["link", "unlink"]),
}) })
type Intent = z.infer<typeof searchParamsSchema>["intent"]
export default async function SASxScandicLoginPage({ export default async function SASxScandicLoginPage({
searchParams, searchParams,
params, params,
@@ -42,11 +45,19 @@ export default async function SASxScandicLoginPage({
const clientId = env.SAS_AUTH_CLIENTID const clientId = env.SAS_AUTH_CLIENTID
const sasLoginHostname = env.SAS_AUTH_ENDPOINT const sasLoginHostname = env.SAS_AUTH_ENDPOINT
const audience = "eb-partner-api" const audience = "eb-partner-api"
// TODO check if this is correct scopes
const scope = encodeURIComponent("openid profile email") const scope = encodeURIComponent("openid profile email")
const loginLink = `${sasLoginHostname}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${urlState}&audience=${audience}` const loginLink = `${sasLoginHostname}/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&state=${urlState}&audience=${audience}`
const intentDescriptions: Record<Intent, string> = {
link: intl.formatMessage({
id: "In order to verify your account linking we will ask you to sign in to your SAS EuroBonus account.",
}),
unlink: intl.formatMessage({
id: "Log in to your SAS Eurobonus account to confirm account unlinking.",
}),
}
return ( return (
<SASModal> <SASModal>
<Redirect url={loginLink} timeout={3000} /> <Redirect url={loginLink} timeout={3000} />
@@ -60,11 +71,7 @@ export default async function SASxScandicLoginPage({
<Title as="h2" level="h1" textTransform="regular"> <Title as="h2" level="h1" textTransform="regular">
{intl.formatMessage({ id: "Redirecting you to SAS" })} {intl.formatMessage({ id: "Redirecting you to SAS" })}
</Title> </Title>
<Body textAlign="center"> <Body textAlign="center">{intentDescriptions[parsedParams.intent]}</Body>
{intl.formatMessage({
id: "In order to verify your account linking we will ask you to sign in to your SAS EuroBonus account.",
})}
</Body>
<Footnote textAlign="center"> <Footnote textAlign="center">
{intl.formatMessage<React.ReactNode>( {intl.formatMessage<React.ReactNode>(
{ {

View File

@@ -2,6 +2,7 @@ import { cookies } from "next/headers"
import { redirect } from "next/navigation" import { redirect } from "next/navigation"
import { z } from "zod" import { z } from "zod"
import { myPages } from "@/constants/routes/myPages"
import { serverClient } from "@/lib/trpc/server" import { serverClient } from "@/lib/trpc/server"
import { getIntl } from "@/i18n" import { getIntl } from "@/i18n"
@@ -12,17 +13,21 @@ import OneTimePasswordForm, {
type OnSubmitHandler, type OnSubmitHandler,
} from "./OneTimePasswordForm" } from "./OneTimePasswordForm"
import type { ReactNode } from "react"
import type { LangParams, PageArgs, SearchParams } from "@/types/params" import type { LangParams, PageArgs, SearchParams } from "@/types/params"
import type { Lang } from "@/constants/languages" import type { Lang } from "@/constants/languages"
const otpError = z.enum(["invalidCode", "expiredCode"]) const otpError = z.enum(["invalidCode", "expiredCode"])
const intent = z.enum(["link", "unlink"])
const searchParamsSchema = z.object({ const searchParamsSchema = z.object({
intent: z.enum(["link"]), intent: intent,
to: z.string(), to: z.string(),
error: otpError.optional(), error: otpError.optional(),
}) })
export type OtpError = z.infer<typeof otpError> export type OtpError = z.infer<typeof otpError>
type Intent = z.infer<typeof intent>
export default async function SASxScandicOneTimePasswordPage({ export default async function SASxScandicOneTimePasswordPage({
searchParams, searchParams,
@@ -88,26 +93,37 @@ export default async function SASxScandicOneTimePasswordPage({
switch (intent) { switch (intent) {
case "link": case "link":
return handleLinkAccount({ lang: params.lang }) return handleLinkAccount({ lang: params.lang })
case "unlink":
return handleUnlinkAccount({ lang: params.lang })
} }
} }
const maskedContactInfo = () => (
<>
<br />
<strong>{to}</strong>
<br />
</>
)
const intentDescriptions: Record<Intent, ReactNode> = {
link: intl.formatMessage<React.ReactNode>(
{
id: "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
},
{ maskedContactInfo }
),
unlink: intl.formatMessage<React.ReactNode>(
{
id: "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
},
{ maskedContactInfo }
),
}
return ( return (
<OneTimePasswordForm <OneTimePasswordForm
heading={intl.formatMessage({ id: "Verification code" })} heading={intl.formatMessage({ id: "Verification code" })}
ingress={intl.formatMessage<React.ReactNode>( ingress={intentDescriptions[intent]}
{
id: "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
},
{
maskedContactInfo: () => (
<>
<br />
<strong>{to}</strong>
<br />
</>
),
}
)}
footnote={intl.formatMessage({ footnote={intl.formatMessage({
id: "This verifcation is needed for additional security.", id: "This verifcation is needed for additional security.",
})} })}
@@ -168,3 +184,34 @@ async function handleLinkAccount({
} }
} }
} }
async function handleUnlinkAccount({
lang,
}: {
lang: Lang
}): ReturnType<OnSubmitHandler> {
const [res, error] = await safeTry(serverClient().partner.sas.unlinkAccount())
if (!res || error) {
console.error("[SAS] unlink account error", error)
return {
url: `/${lang}/sas-x-scandic/error`,
}
}
switch (res.linkingState) {
case "unlinked":
return {
url: `/${lang}/sas-x-scandic/unlink/success`,
type: "replace",
}
case "notLinked":
return {
url: myPages[lang],
}
case "error":
return {
url: `/${lang}/sas-x-scandic/error`,
type: "replace",
}
}
}

View File

@@ -2,8 +2,7 @@ import { z } from "zod"
export const SAS_TOKEN_STORAGE_KEY = "sas-x-scandic-token" export const SAS_TOKEN_STORAGE_KEY = "sas-x-scandic-token"
// TODO nonce??
export const stateSchema = z.object({ export const stateSchema = z.object({
intent: z.literal("link"), intent: z.enum(["link", "unlink"]),
}) })
export type State = z.infer<typeof stateSchema> export type State = z.infer<typeof stateSchema>

View File

@@ -0,0 +1,36 @@
import React from "react"
import { overview } from "@/constants/routes/myPages"
import CheckCircle from "@/components/Icons/CheckCircle"
import { Redirect } from "@/components/Redirect"
import Body from "@/components/TempDesignSystem/Text/Body"
import Title from "@/components/TempDesignSystem/Text/Title"
import { getIntl } from "@/i18n"
import { SASModal } from "../../components/SASModal"
import type { LangParams, PageArgs } from "@/types/params"
export default async function SASxScandicUnlinkSuccessPage({
params,
}: PageArgs<LangParams>) {
const intl = await getIntl()
return (
<SASModal>
<Redirect url={overview[params.lang]} timeout={3000} />
<CheckCircle height={64} width={64} color="uiSemanticSuccess" />
<Title as="h2" level="h1" textAlign="center">
{intl.formatMessage({ id: "Your accounts are now unlinked" })}
</Title>
<div>
<Body textAlign="center">
{intl.formatMessage({
id: "Redirecting you to My Pages.",
})}
</Body>
</div>
</SASModal>
)
}

View File

@@ -1,47 +1,34 @@
"use client" "use client"
import { useRouter } from "next/navigation" import { useParams } from "next/navigation"
import { useIntl } from "react-intl" import { useIntl } from "react-intl"
import { trpc } from "@/lib/trpc/client" import Dialog from "@/components/Dialog"
import Button from "@/components/TempDesignSystem/Button"
import { Loading } from "@/components/Loading" import type { LangParams } from "@/types/params"
import Link from "@/components/TempDesignSystem/Link"
import { toast } from "@/components/TempDesignSystem/Toasts"
export function UnlinkSAS() { export function UnlinkSAS() {
const intl = useIntl() const intl = useIntl()
const router = useRouter() const params = useParams<LangParams>()
const { mutate, isPending } = trpc.partner.sas.unlinkAccount.useMutation({
onSuccess() {
toast.success(intl.formatMessage({ id: "Account unlinked, reloading" }))
// TODO: reload page
router.push("/en/scandic-friends/my-pages")
},
onError() {
toast.error(intl.formatMessage({ id: "Failed to unlink account" }))
},
})
const handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault()
mutate()
}
if (isPending) {
return <Loading color="burgundy" />
}
return ( return (
<Link <Dialog
href="#" titleText={intl.formatMessage({
onClick={handleClick} id: "Are you sure you want to unlink your account?",
color="burgundy" })}
variant="default" // TODO update copy
weight="bold" bodyText={intl.formatMessage({
> id: "We could not connect your accounts to give you access. Please contact us and well help you resolve this issue.",
{intl.formatMessage({ id: "Unlink accounts" })} })}
</Link> cancelButtonText={intl.formatMessage({ id: "Go back" })}
proceedText={intl.formatMessage({ id: "Yes, unlink my accounts" })}
proceedHref={`/${params.lang}/sas-x-scandic/login?intent=unlink`}
trigger={
<Button intent="text" theme="base">
{intl.formatMessage({ id: "Unlink accounts" })}
</Button>
}
/>
) )
} }

View File

@@ -42,8 +42,8 @@ export default async function SASLinkedAccount({
</section> </section>
</SectionContainer> </SectionContainer>
<div className={styles.mutationSection}> <div className={styles.mutationSection}>
<LevelUpgradeButton />
<UnlinkSAS /> <UnlinkSAS />
<LevelUpgradeButton />
</div> </div>
</> </>
) )

View File

@@ -3,6 +3,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 100%;
& .textContainer { & .textContainer {
display: flex; display: flex;
@@ -10,6 +11,10 @@
align-items: center; align-items: center;
gap: var(--Spacing-x1); gap: var(--Spacing-x1);
} }
@media screen and (min-width: 768px) {
width: fit-content;
}
} }
.loading { .loading {

View File

@@ -21,7 +21,7 @@
.mutationSection { .mutationSection {
display: flex; display: flex;
flex-direction: column; flex-direction: column-reverse;
gap: var(--Spacing-x2); gap: var(--Spacing-x2);
align-items: center; align-items: center;

View File

@@ -24,13 +24,11 @@ export default function Dialog({
proceedOnClick = () => {}, proceedOnClick = () => {},
proceedText, proceedText,
titleText, titleText,
triggerButtonText, trigger,
}: DialogProps) { }: DialogProps) {
return ( return (
<DialogTrigger> <DialogTrigger>
<Button intent="secondary" size="small" theme="base"> {trigger}
{triggerButtonText}
</Button>
<ModalOverlay className={styles.overlay} isDismissable> <ModalOverlay className={styles.overlay} isDismissable>
<Modal> <Modal>
<AriaDialog role="alertdialog"> <AriaDialog role="alertdialog">

View File

@@ -133,7 +133,11 @@ export default function Form({ user }: EditFormProps) {
proceedHref={profile[lang]} proceedHref={profile[lang]}
proceedText={intl.formatMessage({ id: "Yes, discard changes" })} proceedText={intl.formatMessage({ id: "Yes, discard changes" })}
titleText={intl.formatMessage({ id: "Discard unsaved changes?" })} titleText={intl.formatMessage({ id: "Discard unsaved changes?" })}
triggerButtonText={intl.formatMessage({ id: "Discard changes" })} trigger={
<Button intent="secondary" size="small" theme="base">
{intl.formatMessage({ id: "Discard changes" })}
</Button>
}
/> />
<Button <Button
disabled={!isValid || methods.formState.isSubmitting} disabled={!isValid || methods.formState.isSubmitting}

View File

@@ -5,6 +5,7 @@ import { trpc } from "@/lib/trpc/client"
import Dialog from "@/components/Dialog" import Dialog from "@/components/Dialog"
import { DeleteIcon } from "@/components/Icons" import { DeleteIcon } from "@/components/Icons"
import Button from "@/components/TempDesignSystem/Button"
import { toast } from "@/components/TempDesignSystem/Toasts" import { toast } from "@/components/TempDesignSystem/Toasts"
import type { DeleteCreditCardConfirmationProps } from "@/types/components/myPages/myProfile/creditCards" import type { DeleteCreditCardConfirmationProps } from "@/types/components/myPages/myProfile/creditCards"
@@ -52,7 +53,11 @@ export default function DeleteCreditCardConfirmation({
proceedOnClick={handleProceedDeleteCard} proceedOnClick={handleProceedDeleteCard}
proceedText={intl.formatMessage({ id: "Yes, remove my card" })} proceedText={intl.formatMessage({ id: "Yes, remove my card" })}
titleText={intl.formatMessage({ id: "Remove card from member profile" })} titleText={intl.formatMessage({ id: "Remove card from member profile" })}
triggerButtonText={<DeleteIcon color="burgundy" />} trigger={
<Button intent="secondary" size="small" theme="base">
<DeleteIcon color="burgundy" />
</Button>
}
/> />
) )
} }

View File

@@ -319,6 +319,7 @@
"Locations": "Placeringer", "Locations": "Placeringer",
"Log in": "Log på", "Log in": "Log på",
"Log in here": "Log ind her", "Log in here": "Log ind her",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Log på/Tilmeld dig", "Log in/Join": "Log på/Tilmeld dig",
"Log out": "Log ud", "Log out": "Log ud",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -441,6 +442,7 @@
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer", "Please enter a valid phone number": "Indtast venligst et gyldigt telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vær opmærksom på, at dette er påkrævet, og at dit kort kun vil blive opkrævet i tilfælde af en no-show.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vær opmærksom på, at dette er påkrævet, og at dit kort kun vil blive opkrævet i tilfælde af en no-show.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Point", "Points": "Point",
@@ -674,6 +676,7 @@
"Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!", "Your Challenges Conquer & Earn!": "Dine udfordringer Overvind og tjen!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din booking er bekræftet, men vi kunne ikke verificere dit medlemskab. Hvis du har booket med et medlemstilbud, skal du enten vise dit eksisterende medlemskab ved check-in, blive medlem eller betale prisdifferencen ved check-in. Tilmelding er foretrukket online før opholdet.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din booking er bekræftet, men vi kunne ikke verificere dit medlemskab. Hvis du har booket med et medlemstilbud, skal du enten vise dit eksisterende medlemskab ved check-in, blive medlem eller betale prisdifferencen ved check-in. Tilmelding er foretrukket online før opholdet.",
"Your card was successfully removed!": "Dit kort blev fjernet!", "Your card was successfully removed!": "Dit kort blev fjernet!",
"Your card was successfully saved!": "Dit kort blev gemt!", "Your card was successfully saved!": "Dit kort blev gemt!",

View File

@@ -320,6 +320,7 @@
"Locations": "Orte", "Locations": "Orte",
"Log in": "Anmeldung", "Log in": "Anmeldung",
"Log in here": "Hier einloggen", "Log in here": "Hier einloggen",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Log in/Anmelden", "Log in/Join": "Log in/Anmelden",
"Log out": "Ausloggen", "Log out": "Ausloggen",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -442,6 +443,7 @@
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein", "Please enter a valid phone number": "Bitte geben Sie eine gültige Telefonnummer ein",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Bitte beachten Sie, dass dies erforderlich ist und dass Ihr Kreditkartenkonto nur in einem No-Show-Fall belastet wird.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Bitte beachten Sie, dass dies erforderlich ist und dass Ihr Kreditkartenkonto nur in einem No-Show-Fall belastet wird.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Punkte", "Points": "Punkte",
@@ -674,6 +676,7 @@
"Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!", "Your Challenges Conquer & Earn!": "Meistern Sie Ihre Herausforderungen und verdienen Sie Geld!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Ihre Buchung ist bestätigt, aber wir konnten Ihr Mitglied nicht verifizieren. Wenn Sie mit einem Mitgliederrabatt gebucht haben, müssen Sie entweder Ihr vorhandenes Mitgliedschaftsnummer bei der Anreise präsentieren, ein Mitglied werden oder die Preisdifferenz bei der Anreise bezahlen. Die Anmeldung ist vorzugsweise online vor der Aufenthaltsdauer erfolgreich.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Ihre Buchung ist bestätigt, aber wir konnten Ihr Mitglied nicht verifizieren. Wenn Sie mit einem Mitgliederrabatt gebucht haben, müssen Sie entweder Ihr vorhandenes Mitgliedschaftsnummer bei der Anreise präsentieren, ein Mitglied werden oder die Preisdifferenz bei der Anreise bezahlen. Die Anmeldung ist vorzugsweise online vor der Aufenthaltsdauer erfolgreich.",
"Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!", "Your card was successfully removed!": "Ihre Karte wurde erfolgreich entfernt!",
"Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!", "Your card was successfully saved!": "Ihre Karte wurde erfolgreich gespeichert!",

View File

@@ -323,6 +323,7 @@
"Locations": "Locations", "Locations": "Locations",
"Log in": "Log in", "Log in": "Log in",
"Log in here": "Log in here", "Log in here": "Log in here",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Log in/Join", "Log in/Join": "Log in/Join",
"Log out": "Log out", "Log out": "Log out",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -446,6 +447,7 @@
"Phone number": "Phone number", "Phone number": "Phone number",
"Please enter a valid phone number": "Please enter a valid phone number", "Please enter a valid phone number": "Please enter a valid phone number",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Points", "Points": "Points",
@@ -680,6 +682,7 @@
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.",
"Your card was successfully removed!": "Your card was successfully removed!", "Your card was successfully removed!": "Your card was successfully removed!",
"Your card was successfully saved!": "Your card was successfully saved!", "Your card was successfully saved!": "Your card was successfully saved!",

View File

@@ -319,6 +319,7 @@
"Locations": "Sijainnit", "Locations": "Sijainnit",
"Log in": "Kirjaudu sisään", "Log in": "Kirjaudu sisään",
"Log in here": "Kirjaudu sisään", "Log in here": "Kirjaudu sisään",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Kirjaudu sisään/Liittyä", "Log in/Join": "Kirjaudu sisään/Liittyä",
"Log out": "Kirjaudu ulos", "Log out": "Kirjaudu ulos",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -441,6 +442,7 @@
"Phone number": "Puhelinnumero", "Phone number": "Puhelinnumero",
"Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero", "Please enter a valid phone number": "Ole hyvä ja näppäile voimassaoleva puhelinnumero",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Huomaa, että tämä on pakollinen, ja että maksukorttiisi kirjataan vain, jos varausmyyntiä ei tapahtu.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Huomaa, että tämä on pakollinen, ja että maksukorttiisi kirjataan vain, jos varausmyyntiä ei tapahtu.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Pisteet", "Points": "Pisteet",
@@ -674,6 +676,7 @@
"Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!", "Your Challenges Conquer & Earn!": "Your Challenges Conquer & Earn!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Varauksesi on vahvistettu, mutta jäsenyytesi ei voitu vahvistaa. Jos olet bookeutunut jäsenyysalennoilla, sinun on joko esitettävä olemassa olevan jäsenyysnumero tarkistukseen, tulla jäseneksi tai maksamaan hinnan eron hotellissa. Jäsenyyden tilittäminen on suositeltavampaa tehdä verkkoon ennen majoittumista.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Varauksesi on vahvistettu, mutta jäsenyytesi ei voitu vahvistaa. Jos olet bookeutunut jäsenyysalennoilla, sinun on joko esitettävä olemassa olevan jäsenyysnumero tarkistukseen, tulla jäseneksi tai maksamaan hinnan eron hotellissa. Jäsenyyden tilittäminen on suositeltavampaa tehdä verkkoon ennen majoittumista.",
"Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!", "Your card was successfully removed!": "Korttisi poistettiin onnistuneesti!",
"Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!", "Your card was successfully saved!": "Korttisi tallennettu onnistuneesti!",

View File

@@ -318,6 +318,7 @@
"Locations": "Steder", "Locations": "Steder",
"Log in": "Logg Inn", "Log in": "Logg Inn",
"Log in here": "Logg inn her", "Log in here": "Logg inn her",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Logg på/Bli med", "Log in/Join": "Logg på/Bli med",
"Log out": "Logg ut", "Log out": "Logg ut",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -440,6 +441,7 @@
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer", "Please enter a valid phone number": "Vennligst oppgi et gyldig telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vær oppmerksom på at dette er påkrevd, og at ditt kredittkort kun vil bli belastet i tilfelle av en no-show.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vær oppmerksom på at dette er påkrevd, og at ditt kredittkort kun vil bli belastet i tilfelle av en no-show.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Poeng", "Points": "Poeng",
@@ -672,6 +674,7 @@
"Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!", "Your Challenges Conquer & Earn!": "Dine utfordringer Erobre og tjen!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din bestilling er bekreftet, men vi kunne ikke verifisere medlemskapet ditt. Hvis du har booke ut med et medlemsrabatt, må du enten presentere eksisterende medlemsnummer ved check-in, bli medlem eller betale prisdifferansen ved hotellet. Registrering er foretrukket gjort online før oppholdet.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din bestilling er bekreftet, men vi kunne ikke verifisere medlemskapet ditt. Hvis du har booke ut med et medlemsrabatt, må du enten presentere eksisterende medlemsnummer ved check-in, bli medlem eller betale prisdifferansen ved hotellet. Registrering er foretrukket gjort online før oppholdet.",
"Your card was successfully removed!": "Kortet ditt ble fjernet!", "Your card was successfully removed!": "Kortet ditt ble fjernet!",
"Your card was successfully saved!": "Kortet ditt ble lagret!", "Your card was successfully saved!": "Kortet ditt ble lagret!",

View File

@@ -318,6 +318,7 @@
"Locations": "Platser", "Locations": "Platser",
"Log in": "Logga in", "Log in": "Logga in",
"Log in here": "Logga in här", "Log in here": "Logga in här",
"Log in to your SAS Eurobonus account to confirm account unlinking.": "Log in to your SAS Eurobonus account to confirm account unlinking.",
"Log in/Join": "Logga in/Gå med", "Log in/Join": "Logga in/Gå med",
"Log out": "Logga ut", "Log out": "Logga ut",
"Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}", "Long {long} ∙ Lat {lat}": "Long {long} ∙ Lat {lat}",
@@ -440,6 +441,7 @@
"Phone number": "Telefonnummer", "Phone number": "Telefonnummer",
"Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer", "Please enter a valid phone number": "Var vänlig och ange ett giltigt telefonnummer",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.", "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to confirm your account linking.",
"Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.": "Please enter the code sent to <maskedContactInfo></maskedContactInfo> in order to unlink your accounts.",
"Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vänligen notera att detta är obligatoriskt, och att ditt kreditkort endast debiteras i händelse av en no-show.", "Please note that this is mandatory, and that your card will only be charged in the event of a no-show.": "Vänligen notera att detta är obligatoriskt, och att ditt kreditkort endast debiteras i händelse av en no-show.",
"Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.", "Please try and change your search for this destination or see alternative hotels.": "Please try and change your search for this destination or see alternative hotels.",
"Points": "Poäng", "Points": "Poäng",
@@ -672,6 +674,7 @@
"Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!", "Your Challenges Conquer & Earn!": "Dina utmaningar Erövra och tjäna!",
"Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level", "Your SAS level has upgraded your friends level": "Your SAS level has upgraded your friends level",
"Your accounts are connected": "Your accounts are connected", "Your accounts are connected": "Your accounts are connected",
"Your accounts are now unlinked": "Your accounts are now unlinked",
"Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din bokning är bekräftad, men vi kunde inte verifiera ditt medlemskap. Om du har bokat med ett medlemsrabatt måste du antingen presentera ditt befintliga medlemsnummer vid check-in, bli medlem eller betala prisdifferensen vid hotell. Registrering är föredragen gjord online före vistelsen.", "Your booking(s) is confirmed but we could not verify your membership. If you have booked with a member discount, you'll either need to present your existing membership number upon check-in, become a member or pay the price difference at the hotel. Signing up is preferably done online before the stay.": "Din bokning är bekräftad, men vi kunde inte verifiera ditt medlemskap. Om du har bokat med ett medlemsrabatt måste du antingen presentera ditt befintliga medlemsnummer vid check-in, bli medlem eller betala prisdifferensen vid hotell. Registrering är föredragen gjord online före vistelsen.",
"Your card was successfully removed!": "Ditt kort har tagits bort!", "Your card was successfully removed!": "Ditt kort har tagits bort!",
"Your card was successfully saved!": "Ditt kort har sparats!", "Your card was successfully saved!": "Ditt kort har sparats!",

View File

@@ -163,6 +163,8 @@ export namespace endpoints {
export const subscriberId = `${base.path.profile}/${version}/${base.enitity.Profile}/SubscriberId` export const subscriberId = `${base.path.profile}/${version}/${base.enitity.Profile}/SubscriberId`
export const link = `${base.path.profile}/${version}/${base.enitity.Profile}/link` export const link = `${base.path.profile}/${version}/${base.enitity.Profile}/link`
export const unlink = `${base.path.profile}/${version}/${base.enitity.Profile}/Unlink`
// TODO: Remove once new endpoints are out in production. // TODO: Remove once new endpoints are out in production.
export const reward = `${base.path.profile}/${version}/${base.enitity.Profile}/reward` export const reward = `${base.path.profile}/${version}/${base.enitity.Profile}/reward`
export const tierRewards = `${base.path.profile}/${version}/${base.enitity.Profile}/tierRewards` export const tierRewards = `${base.path.profile}/${version}/${base.enitity.Profile}/tierRewards`

View File

@@ -1,27 +1,50 @@
import { TRPCError } from "@trpc/server"
import { z } from "zod" import { z } from "zod"
import * as api from "@/lib/api"
import { protectedProcedure } from "@/server/trpc" import { protectedProcedure } from "@/server/trpc"
import { timeout } from "@/utils/timeout" import { getSasToken } from "./getSasToken"
const outputSchema = z.object({ const outputSchema = z.object({
// unlinked: z.boolean(), linkingState: z.enum(["unlinked", "notLinked", "error"]),
}) })
export const unlinkAccount = protectedProcedure export const unlinkAccount = protectedProcedure
.output(outputSchema) .output(outputSchema)
.mutation(async function ({ ctx, input }) { .mutation(async function ({ ctx }) {
console.log("[SAS] unlink account") const sasAuthToken = getSasToken()
await timeout(1000)
//TODO: Call actual API here
throw new TRPCError({ const apiResponse = await api.post(api.endpoints.v1.Profile.unlink, {
message: "Unable to unlink account", headers: {
code: "BAD_REQUEST", Authorization: `Bearer ${ctx.session.token.access_token}`,
},
body: {
partner: "sas_eb",
partnerSpecific: {
eurobonusAccessToken: sasAuthToken,
},
},
}) })
return { if (apiResponse.status === 204) {
unlinked: true, console.log("[SAS] unlink account success")
return { linkingState: "unlinked" }
} }
if (apiResponse.status === 400) {
const result = await apiResponse.json()
console.log("[SAS] unlink account error with response", result)
return { linkingState: "error" }
}
if (apiResponse.status === 404) {
console.log("[SAS] tried unlinking an account that was not linked")
return { linkingState: "notLinked" }
}
console.log(
`[SAS] unlink account error with status code ${apiResponse.status} and response ${await apiResponse.text()}`
)
return { linkingState: "error" }
}) })

View File

@@ -6,5 +6,5 @@ export interface DialogProps {
proceedOnClick?: (close: () => void) => void proceedOnClick?: (close: () => void) => void
proceedText: string proceedText: string
titleText: string titleText: string
triggerButtonText: React.ReactNode trigger: React.ReactNode
} }