Merged in feat/LOY-364-promo-activation-flow (pull request #2872)
Feat/LOY-364 promo activation flow * feat(LOY-364): add promo activation flow * chore(LOY-371): add tracking Approved-by: Chuma Mcphoy (We Ahead)
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
.activatedText {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
padding-top: var(--Space-x2);
|
||||||
|
color: var(--Text-Default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import styles from "./campaignActivated.module.css"
|
||||||
|
|
||||||
|
export function CampaignActivated() {
|
||||||
|
const intl = useIntl()
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Divider color="Border/Divider/Brand/OnPrimary 3/Default" />
|
||||||
|
<span className={styles.activatedText}>
|
||||||
|
<MaterialIcon icon="info" color="Icon/Intense" size={24} />
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "This campaign is active",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
.errorModal {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contactDetails {
|
||||||
|
padding: 0 var(--Space-x1);
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
display: grid;
|
||||||
|
gap: var(--Space-x05);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.divider {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--Space-x2);
|
||||||
|
color: var(--UI-Text-Placeholder);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Divider } from "@scandic-hotels/design-system/Divider"
|
||||||
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
|
import Link from "@scandic-hotels/design-system/Link"
|
||||||
|
import Modal from "@scandic-hotels/design-system/Modal"
|
||||||
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
|
||||||
|
import { supportEmail, supportPhone } from "@/constants/contactSupport"
|
||||||
|
|
||||||
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
|
import styles from "./errorModal.module.css"
|
||||||
|
|
||||||
|
import type { PromoCode } from "@scandic-hotels/trpc/types/promoCampaignPage"
|
||||||
|
|
||||||
|
interface ErrorModalProps {
|
||||||
|
promoCode: PromoCode
|
||||||
|
isOpen: boolean
|
||||||
|
handleToggle: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorModal({
|
||||||
|
promoCode,
|
||||||
|
isOpen,
|
||||||
|
handleToggle,
|
||||||
|
}: ErrorModalProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const lang = useLang()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
className={styles.errorModal}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onToggle={handleToggle}
|
||||||
|
>
|
||||||
|
<MaterialIcon icon="info" color="Icon/Feedback/Error" size={64} />
|
||||||
|
<Typography variant="Title/Subtitle/lg">
|
||||||
|
<h3>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Could not activate this offer",
|
||||||
|
})}
|
||||||
|
</h3>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Read more",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
<div className={styles.divider}>
|
||||||
|
<Divider color="Border/Divider/Subtle" />
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<h4>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "If this problem persists",
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</Typography>
|
||||||
|
<Divider color="Border/Divider/Subtle" />
|
||||||
|
</div>
|
||||||
|
<span className={styles.contactDetails}>
|
||||||
|
<Typography variant="Title/Subtitle/md">
|
||||||
|
<h4>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Contact us",
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Link/md" className={styles.address}>
|
||||||
|
<address>
|
||||||
|
<Link
|
||||||
|
href={`tel:${supportPhone[lang].replaceAll(" ", "")}`}
|
||||||
|
color="Text/Interactive/Secondary"
|
||||||
|
>
|
||||||
|
{supportPhone[lang]}
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
href={`mailto:${supportEmail[lang]}`}
|
||||||
|
color="Text/Interactive/Secondary"
|
||||||
|
>
|
||||||
|
{supportEmail[lang]}
|
||||||
|
</Link>
|
||||||
|
</address>
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="Body/Paragraph/mdRegular">
|
||||||
|
<p>
|
||||||
|
{intl.formatMessage(
|
||||||
|
{
|
||||||
|
defaultMessage: "Campaign reference: <bold>{promoCode}</bold>",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
promoCode,
|
||||||
|
bold: (text) => (
|
||||||
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
|
<span>{text}</span>
|
||||||
|
</Typography>
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</Typography>
|
||||||
|
</span>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.activateButton {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"use client"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { useIntl } from "react-intl"
|
||||||
|
|
||||||
|
import { Button } from "@scandic-hotels/design-system/Button"
|
||||||
|
import { trackEvent } from "@scandic-hotels/tracking/base"
|
||||||
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
|
||||||
|
import { CampaignActivated } from "./CampaignActivated"
|
||||||
|
import { ErrorModal } from "./ErrorModal"
|
||||||
|
|
||||||
|
import styles from "./activateOffer.module.css"
|
||||||
|
|
||||||
|
import type { PromoCode } from "@scandic-hotels/trpc/types/promoCampaignPage"
|
||||||
|
|
||||||
|
interface ActivateOfferProps {
|
||||||
|
promoCode: PromoCode
|
||||||
|
campaignIsActive: boolean
|
||||||
|
}
|
||||||
|
export default function ActivateOffer({
|
||||||
|
promoCode,
|
||||||
|
campaignIsActive,
|
||||||
|
}: ActivateOfferProps) {
|
||||||
|
const intl = useIntl()
|
||||||
|
const router = useRouter()
|
||||||
|
const trpcUtils = trpc.useUtils()
|
||||||
|
|
||||||
|
async function handleActivateFlow() {
|
||||||
|
trackEvent({
|
||||||
|
event: "ActivatePromoCampaign",
|
||||||
|
login: {
|
||||||
|
ctaName: "activate offer",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
activateCampaign.mutate({ promotionId: promoCode })
|
||||||
|
}
|
||||||
|
|
||||||
|
const activateCampaign = trpc.user.promoCampaign.add.useMutation({
|
||||||
|
onSuccess: async (data) => {
|
||||||
|
if (!data) return
|
||||||
|
trpcUtils.user.promoCampaign.invalidate()
|
||||||
|
router.refresh()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function ActivateButton() {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={handleActivateFlow}
|
||||||
|
variant="Tertiary"
|
||||||
|
size="Medium"
|
||||||
|
typography="Body/Paragraph/mdRegular"
|
||||||
|
className={styles.activateButton}
|
||||||
|
isDisabled={activateCampaign.isPending}
|
||||||
|
isPending={activateCampaign.isPending}
|
||||||
|
wrapping
|
||||||
|
>
|
||||||
|
{intl.formatMessage({
|
||||||
|
defaultMessage: "Activate offer",
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{campaignIsActive ||
|
||||||
|
(activateCampaign.isSuccess && activateCampaign.data) ? (
|
||||||
|
<CampaignActivated />
|
||||||
|
) : (
|
||||||
|
<ActivateButton />
|
||||||
|
)}
|
||||||
|
<ErrorModal
|
||||||
|
promoCode={promoCode}
|
||||||
|
isOpen={
|
||||||
|
activateCampaign.isError ||
|
||||||
|
(activateCampaign.isSuccess && !activateCampaign.data)
|
||||||
|
}
|
||||||
|
handleToggle={activateCampaign.reset}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import { useIntl } from "react-intl"
|
|
||||||
|
|
||||||
import { Button } from "@scandic-hotels/design-system/Button"
|
|
||||||
|
|
||||||
import styles from "../hero.module.css"
|
|
||||||
|
|
||||||
// TODO: Trigger acivation.
|
|
||||||
export default function ActivateOfferButton() {
|
|
||||||
const intl = useIntl()
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="Tertiary"
|
|
||||||
size="Medium"
|
|
||||||
typography="Body/Paragraph/mdRegular"
|
|
||||||
className={styles.activateButton}
|
|
||||||
>
|
|
||||||
{intl.formatMessage({
|
|
||||||
defaultMessage: "Activate offer",
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import { useIntl } from "react-intl"
|
|||||||
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
|
import { login } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||||
import { useLazyPathname } from "@scandic-hotels/common/hooks/useLazyPathname"
|
import { useLazyPathname } from "@scandic-hotels/common/hooks/useLazyPathname"
|
||||||
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
import ButtonLink from "@scandic-hotels/design-system/ButtonLink"
|
||||||
|
import { trackEvent } from "@scandic-hotels/tracking/base"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
|
|
||||||
@@ -18,6 +19,16 @@ export default function PromoLoginButton() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
|
onClick={() =>
|
||||||
|
trackEvent({
|
||||||
|
event: "loginStart",
|
||||||
|
login: {
|
||||||
|
position: "campaign banner login cta",
|
||||||
|
action: "login start",
|
||||||
|
ctaName: "login",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
href={loginHref}
|
href={loginHref}
|
||||||
variant="Primary"
|
variant="Primary"
|
||||||
color="Inverted"
|
color="Inverted"
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import { Divider } from "@scandic-hotels/design-system/Divider"
|
|||||||
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
import { MaterialIcon } from "@scandic-hotels/design-system/Icons/MaterialIcon"
|
||||||
import Image from "@scandic-hotels/design-system/Image"
|
import Image from "@scandic-hotels/design-system/Image"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
|
import { trackEvent } from "@scandic-hotels/tracking/base"
|
||||||
|
|
||||||
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
import { getProfileSafely } from "@/lib/trpc/memoizedRequests"
|
||||||
|
|
||||||
import { getIntl } from "@/i18n"
|
import { getIntl } from "@/i18n"
|
||||||
import { getLang } from "@/i18n/serverContext"
|
import { getLang } from "@/i18n/serverContext"
|
||||||
|
|
||||||
import ActivateOfferButton from "./ActivateOfferButton"
|
import ActivateOffer from "./ActivateOffer"
|
||||||
import IneligibleMessage from "./IneligibleMessage"
|
import IneligibleMessage from "./IneligibleMessage"
|
||||||
import PromoLoginButton from "./PromoLoginButton"
|
import PromoLoginButton from "./PromoLoginButton"
|
||||||
import { isUserEligibleForPromo } from "./utils"
|
import { isUserEligibleForPromo } from "./utils"
|
||||||
@@ -20,28 +21,52 @@ import { isUserEligibleForPromo } from "./utils"
|
|||||||
import styles from "./hero.module.css"
|
import styles from "./hero.module.css"
|
||||||
|
|
||||||
import type { MembershipLevel } from "@scandic-hotels/common/constants/membershipLevels"
|
import type { MembershipLevel } from "@scandic-hotels/common/constants/membershipLevels"
|
||||||
import type { PromoHero } from "@scandic-hotels/trpc/types/promoCampaignPage"
|
import type {
|
||||||
|
PromoCode,
|
||||||
|
PromoHero,
|
||||||
|
} from "@scandic-hotels/trpc/types/promoCampaignPage"
|
||||||
|
|
||||||
interface PromoCampaignHeroProps extends React.HTMLAttributes<HTMLDivElement> {
|
interface PromoCampaignHeroProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
promoHero: PromoHero
|
promoHero: PromoHero
|
||||||
eligibleLevels: MembershipLevel[]
|
eligibleLevels: MembershipLevel[]
|
||||||
|
promoCode: PromoCode
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function PromoCampaignHero({
|
export default async function PromoCampaignHero({
|
||||||
promoHero,
|
promoHero,
|
||||||
eligibleLevels,
|
eligibleLevels,
|
||||||
|
promoCode,
|
||||||
className,
|
className,
|
||||||
...props
|
...props
|
||||||
}: PromoCampaignHeroProps) {
|
}: PromoCampaignHeroProps) {
|
||||||
const { image, heading, benefits } = promoHero
|
const intl = await getIntl()
|
||||||
|
const lang = await getLang()
|
||||||
const profile = await getProfileSafely()
|
const profile = await getProfileSafely()
|
||||||
|
|
||||||
|
const { image, heading, benefits } = promoHero
|
||||||
const isLoggedIn = !!profile
|
const isLoggedIn = !!profile
|
||||||
const userMembershipLevel = profile?.membership?.membershipLevel
|
const userMembershipLevel = profile?.membership?.membershipLevel
|
||||||
const isEligible = isLoggedIn
|
const isEligible = isLoggedIn
|
||||||
? isUserEligibleForPromo(userMembershipLevel, eligibleLevels)
|
? isUserEligibleForPromo(userMembershipLevel, eligibleLevels)
|
||||||
: false
|
: false
|
||||||
const intl = await getIntl()
|
|
||||||
const lang = await getLang()
|
const activeCampaigns = profile?.promotions
|
||||||
|
const campaignAlreadyActive = Boolean(activeCampaigns?.includes(promoCode))
|
||||||
|
|
||||||
|
function CampaignCTA() {
|
||||||
|
if (!isLoggedIn) return null
|
||||||
|
|
||||||
|
if (!isEligible) {
|
||||||
|
return <IneligibleMessage />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ActivateOffer
|
||||||
|
promoCode={promoCode}
|
||||||
|
campaignIsActive={campaignAlreadyActive}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
@@ -93,9 +118,7 @@ export default async function PromoCampaignHero({
|
|||||||
</ul>
|
</ul>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
<CampaignCTA />
|
||||||
{isLoggedIn &&
|
|
||||||
(isEligible ? <ActivateOfferButton /> : <IneligibleMessage />)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isLoggedIn && (
|
{!isLoggedIn && (
|
||||||
@@ -126,6 +149,16 @@ export default async function PromoCampaignHero({
|
|||||||
<Divider color="white" />
|
<Divider color="white" />
|
||||||
</div>
|
</div>
|
||||||
<ButtonLink
|
<ButtonLink
|
||||||
|
onClick={() =>
|
||||||
|
trackEvent({
|
||||||
|
event: "signupStart",
|
||||||
|
signUp: {
|
||||||
|
position: "campaign banner signup cta",
|
||||||
|
action: "signup start",
|
||||||
|
ctaName: "signup",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
href={signup[lang]}
|
href={signup[lang]}
|
||||||
variant="Secondary"
|
variant="Secondary"
|
||||||
color="Inverted"
|
color="Inverted"
|
||||||
|
|||||||
@@ -28,8 +28,15 @@ export default async function PromoCampaignPage() {
|
|||||||
}
|
}
|
||||||
const { promo_campaign_page, tracking } = pageData
|
const { promo_campaign_page, tracking } = pageData
|
||||||
|
|
||||||
const { heading, subheading, enddate, promo_hero, blocks, eligibleLevels } =
|
const {
|
||||||
promo_campaign_page
|
heading,
|
||||||
|
subheading,
|
||||||
|
enddate,
|
||||||
|
promo_hero,
|
||||||
|
blocks,
|
||||||
|
eligibleLevels,
|
||||||
|
promo_code,
|
||||||
|
} = promo_campaign_page
|
||||||
|
|
||||||
const isCampaignExpired = enddate
|
const isCampaignExpired = enddate
|
||||||
? dt().isAfter(dt(enddate).endOf("day"))
|
? dt().isAfter(dt(enddate).endOf("day"))
|
||||||
@@ -47,6 +54,7 @@ export default async function PromoCampaignPage() {
|
|||||||
promoHero={promo_hero}
|
promoHero={promo_hero}
|
||||||
eligibleLevels={eligibleLevels}
|
eligibleLevels={eligibleLevels}
|
||||||
blocks={blocks}
|
blocks={blocks}
|
||||||
|
promoCode={promo_code}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
@@ -61,18 +69,21 @@ function ActiveCampaignLayout({
|
|||||||
promoHero,
|
promoHero,
|
||||||
eligibleLevels,
|
eligibleLevels,
|
||||||
blocks,
|
blocks,
|
||||||
|
promoCode,
|
||||||
}: {
|
}: {
|
||||||
heading: PromoCampaignPageData["heading"]
|
heading: PromoCampaignPageData["heading"]
|
||||||
subheading: PromoCampaignPageData["subheading"]
|
subheading: PromoCampaignPageData["subheading"]
|
||||||
promoHero: PromoHero
|
promoHero: PromoHero
|
||||||
eligibleLevels: PromoCampaignPageData["eligibleLevels"]
|
eligibleLevels: PromoCampaignPageData["eligibleLevels"]
|
||||||
blocks: PromoCampaignPageData["blocks"]
|
blocks: PromoCampaignPageData["blocks"]
|
||||||
|
promoCode: PromoCampaignPageData["promo_code"]
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.pageContainer, styles.active)}>
|
<div className={cx(styles.pageContainer, styles.active)}>
|
||||||
<PromoCampaignHero
|
<PromoCampaignHero
|
||||||
promoHero={promoHero}
|
promoHero={promoHero}
|
||||||
eligibleLevels={eligibleLevels}
|
eligibleLevels={eligibleLevels}
|
||||||
|
promoCode={promoCode}
|
||||||
/>
|
/>
|
||||||
<div className={styles.intro}>
|
<div className={styles.intro}>
|
||||||
<div className={styles.headingWrapper}>
|
<div className={styles.headingWrapper}>
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ const authenticatedUser: SafeUser = {
|
|||||||
phoneNumber: undefined,
|
phoneNumber: undefined,
|
||||||
profileId: "",
|
profileId: "",
|
||||||
employmentDetails: undefined,
|
employmentDetails: undefined,
|
||||||
|
promotions: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const badAuthenticatedUser: SafeUser = {
|
const badAuthenticatedUser: SafeUser = {
|
||||||
@@ -193,6 +194,7 @@ const badAuthenticatedUser: SafeUser = {
|
|||||||
phoneNumber: undefined,
|
phoneNumber: undefined,
|
||||||
profileId: "",
|
profileId: "",
|
||||||
employmentDetails: undefined,
|
employmentDetails: undefined,
|
||||||
|
promotions: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
const loggedOutGuest: Guest = {
|
const loggedOutGuest: Guest = {
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ export namespace endpoints {
|
|||||||
*/
|
*/
|
||||||
export namespace Profile {
|
export namespace Profile {
|
||||||
export const profile = `${base.path.profile}/${version}/${base.enitity.Profile}`
|
export const profile = `${base.path.profile}/${version}/${base.enitity.Profile}`
|
||||||
|
export const promoCampaign = `${profile}/Promotion`
|
||||||
|
|
||||||
export function teamMemberCard(employeeId: string) {
|
export function teamMemberCard(employeeId: string) {
|
||||||
return `${profile}/${employeeId}/TeamMemberCard`
|
return `${profile}/${employeeId}/TeamMemberCard`
|
||||||
|
|||||||
@@ -62,3 +62,7 @@ export const getSavedPaymentCardsInput = z.object({
|
|||||||
export type GetSavedPaymentCardsInput = z.input<
|
export type GetSavedPaymentCardsInput = z.input<
|
||||||
typeof getSavedPaymentCardsInput
|
typeof getSavedPaymentCardsInput
|
||||||
>
|
>
|
||||||
|
|
||||||
|
export const addPromoCampaignInput = z.object({
|
||||||
|
promotionId: z.string(),
|
||||||
|
})
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { serverErrorByStatus } from "../../errors"
|
|||||||
import { protectedProcedure, serviceProcedure } from "../../procedures"
|
import { protectedProcedure, serviceProcedure } from "../../procedures"
|
||||||
import {
|
import {
|
||||||
addCreditCardInput,
|
addCreditCardInput,
|
||||||
|
addPromoCampaignInput,
|
||||||
deleteCreditCardInput,
|
deleteCreditCardInput,
|
||||||
saveCreditCardInput,
|
saveCreditCardInput,
|
||||||
signupInput,
|
signupInput,
|
||||||
@@ -216,4 +217,48 @@ export const userMutationRouter = router({
|
|||||||
redirectUrl: signupVerify[input.language],
|
redirectUrl: signupVerify[input.language],
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
promoCampaign: router({
|
||||||
|
add: protectedProcedure
|
||||||
|
.input(addPromoCampaignInput)
|
||||||
|
.mutation(async function ({ ctx, input }) {
|
||||||
|
userMutationLogger.info("api.user.promoCampaign.add start")
|
||||||
|
const apiResponse = await api.post(
|
||||||
|
api.endpoints.v2.Profile.promoCampaign,
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${ctx.session.token.access_token}`,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
promotionId: input.promotionId,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!apiResponse.ok) {
|
||||||
|
const text = await apiResponse.text()
|
||||||
|
userMutationLogger.error(
|
||||||
|
"api.user.promoCampaign.add error",
|
||||||
|
JSON.stringify({
|
||||||
|
query: {
|
||||||
|
promotionId: input.promotionId,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
status: apiResponse.status,
|
||||||
|
statusText: apiResponse.statusText,
|
||||||
|
text,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
userMutationLogger.info(
|
||||||
|
"api.user.promoCampaign.add success",
|
||||||
|
JSON.stringify({
|
||||||
|
query: { promotionId: input.promotionId },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return true
|
||||||
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ export const getUserSchema = z
|
|||||||
.nullable(),
|
.nullable(),
|
||||||
loyalty: userLoyaltySchema.optional(),
|
loyalty: userLoyaltySchema.optional(),
|
||||||
employmentDetails: employmentDetailsSchema,
|
employmentDetails: employmentDetailsSchema,
|
||||||
|
promotions: z.array(z.string()).nullish(),
|
||||||
}),
|
}),
|
||||||
type: z.string(),
|
type: z.string(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ export const getVerifiedUser = cache(
|
|||||||
)
|
)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const verifiedData = getUserSchema.safeParse(apiJson)
|
const verifiedData = getUserSchema.safeParse(apiJson)
|
||||||
|
|
||||||
if (!verifiedData.success) {
|
if (!verifiedData.success) {
|
||||||
metricsGetVerifiedUser.validationError(verifiedData.error)
|
metricsGetVerifiedUser.validationError(verifiedData.error)
|
||||||
return null
|
return null
|
||||||
@@ -224,6 +224,7 @@ export function parsedUser(data: User, isMFA: boolean) {
|
|||||||
dateOfBirth: data.dateOfBirth,
|
dateOfBirth: data.dateOfBirth,
|
||||||
email: data.email,
|
email: data.email,
|
||||||
employmentDetails: data.employmentDetails,
|
employmentDetails: data.employmentDetails,
|
||||||
|
promotions: data.promotions || null,
|
||||||
firstName: data.firstName,
|
firstName: data.firstName,
|
||||||
language: data.language,
|
language: data.language,
|
||||||
lastName: data.lastName,
|
lastName: data.lastName,
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface PromoCampaignPage
|
|||||||
export type PromoCampaignPageData = PromoCampaignPage["promo_campaign_page"]
|
export type PromoCampaignPageData = PromoCampaignPage["promo_campaign_page"]
|
||||||
|
|
||||||
export type PromoHero = NonNullable<PromoCampaignPageData["promo_hero"]>
|
export type PromoHero = NonNullable<PromoCampaignPageData["promo_hero"]>
|
||||||
|
export type PromoCode = NonNullable<PromoCampaignPageData["promo_code"]>
|
||||||
|
|
||||||
/* REFS */
|
/* REFS */
|
||||||
export interface GetPromoCampaignPageRefsData
|
export interface GetPromoCampaignPageRefsData
|
||||||
|
|||||||
Reference in New Issue
Block a user