feat/SW-3108 external links
* feat(SW-3108): Added external link options to shortcuts * feat(SW-3108): Added external link options to header Approved-by: Matilda Landström
This commit is contained in:
@@ -24,7 +24,7 @@ export default function ShortcutsListItems({
|
|||||||
className={styles.link}
|
className={styles.link}
|
||||||
>
|
>
|
||||||
<Typography variant="Body/Paragraph/mdBold">
|
<Typography variant="Body/Paragraph/mdBold">
|
||||||
<span>{shortcut.text || shortcut.title}</span>
|
<span>{shortcut.text}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<MaterialIcon color="CurrentColor" icon="arrow_forward" />
|
<MaterialIcon color="CurrentColor" icon="arrow_forward" />
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ export default function MegaMenu({
|
|||||||
) : null}
|
) : null}
|
||||||
<div className={styles.megaMenuContent}>
|
<div className={styles.megaMenuContent}>
|
||||||
<div className={styles.seeAllLink}>
|
<div className={styles.seeAllLink}>
|
||||||
{seeAllLink?.link ? (
|
{seeAllLink?.url ? (
|
||||||
<Link
|
<Link
|
||||||
href={seeAllLink.link.url}
|
href={seeAllLink.url}
|
||||||
variant="icon"
|
variant="icon"
|
||||||
weight="bold"
|
weight="bold"
|
||||||
onClick={handleNavigate}
|
onClick={handleNavigate}
|
||||||
@@ -75,11 +75,11 @@ export default function MegaMenu({
|
|||||||
<span className={styles.submenuTitle}>{item.title}</span>
|
<span className={styles.submenuTitle}>{item.title}</span>
|
||||||
</Typography>
|
</Typography>
|
||||||
<ul className={styles.submenu}>
|
<ul className={styles.submenu}>
|
||||||
{item.links.map(({ title, link }) =>
|
{item.links.map(({ title, url }) =>
|
||||||
link ? (
|
url ? (
|
||||||
<li key={title} className={styles.submenuItem}>
|
<li key={title} className={styles.submenuItem}>
|
||||||
<Link
|
<Link
|
||||||
href={link.url}
|
href={url}
|
||||||
variant="menu"
|
variant="menu"
|
||||||
className={styles.link}
|
className={styles.link}
|
||||||
onClick={handleNavigate}
|
onClick={handleNavigate}
|
||||||
|
|||||||
@@ -42,52 +42,58 @@ export default function MenuItem({ item, isMobile }: NavigationMenuItemProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return submenu.length ? (
|
if (submenu.length) {
|
||||||
<>
|
return (
|
||||||
<MainMenuButton
|
<>
|
||||||
onClick={() => toggleMegaMenu(megaMenuTitle)}
|
<MainMenuButton
|
||||||
|
onClick={() => toggleMegaMenu(megaMenuTitle)}
|
||||||
|
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : ""}`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
{isMobile ? (
|
||||||
|
<MaterialIcon
|
||||||
|
icon="arrow_forward_ios"
|
||||||
|
size={20}
|
||||||
|
className={`${styles.chevron}`}
|
||||||
|
color="Icon/Interactive/Accent"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MaterialIcon
|
||||||
|
icon="keyboard_arrow_down"
|
||||||
|
size={20}
|
||||||
|
className={`${styles.chevron} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
||||||
|
color="Icon/Interactive/Accent"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</MainMenuButton>
|
||||||
|
<div
|
||||||
|
ref={megaMenuRef}
|
||||||
|
className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
||||||
|
>
|
||||||
|
<MegaMenu
|
||||||
|
isMobile={isMobile}
|
||||||
|
title={title}
|
||||||
|
seeAllLink={seeAllLink}
|
||||||
|
submenu={submenu}
|
||||||
|
card={card}
|
||||||
|
isOpen={isMegaMenuOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
} else if (link?.url) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : ""}`}
|
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : ""}`}
|
||||||
|
variant="navigation"
|
||||||
|
weight="bold"
|
||||||
|
onClick={handleNavigate}
|
||||||
|
href={link.url}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
{isMobile ? (
|
</Link>
|
||||||
<MaterialIcon
|
)
|
||||||
icon="arrow_forward_ios"
|
}
|
||||||
size={20}
|
|
||||||
className={`${styles.chevron}`}
|
return null
|
||||||
color="Icon/Interactive/Accent"
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MaterialIcon
|
|
||||||
icon="keyboard_arrow_down"
|
|
||||||
size={20}
|
|
||||||
className={`${styles.chevron} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
|
||||||
color="Icon/Interactive/Accent"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</MainMenuButton>
|
|
||||||
<div
|
|
||||||
ref={megaMenuRef}
|
|
||||||
className={`${styles.dropdown} ${isMegaMenuOpen ? styles.isExpanded : ""}`}
|
|
||||||
>
|
|
||||||
<MegaMenu
|
|
||||||
isMobile={isMobile}
|
|
||||||
title={title}
|
|
||||||
seeAllLink={seeAllLink}
|
|
||||||
submenu={submenu}
|
|
||||||
card={card}
|
|
||||||
isOpen={isMegaMenuOpen}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<Link
|
|
||||||
className={`${styles.navigationMenuItem} ${isMobile ? styles.mobile : ""}`}
|
|
||||||
variant="navigation"
|
|
||||||
weight="bold"
|
|
||||||
onClick={handleNavigate}
|
|
||||||
href={link!.url}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,13 +11,13 @@ export default function TopLink({
|
|||||||
}: TopLinkProps) {
|
}: TopLinkProps) {
|
||||||
const linkData = isLoggedIn ? topLink.logged_in : topLink.logged_out
|
const linkData = isLoggedIn ? topLink.logged_in : topLink.logged_out
|
||||||
|
|
||||||
if (!linkData?.link?.url || !linkData?.title) {
|
if (!linkData?.url || !linkData?.title) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderLink
|
<HeaderLink
|
||||||
href={linkData.link.url}
|
href={linkData.url}
|
||||||
iconName={linkData.icon || IconName.Gift}
|
iconName={linkData.icon || IconName.Gift}
|
||||||
iconSize={iconSize}
|
iconSize={iconSize}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ fragment Shortcuts on Shortcuts {
|
|||||||
title
|
title
|
||||||
two_column_list
|
two_column_list
|
||||||
shortcuts {
|
shortcuts {
|
||||||
|
is_contentstack_link
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
open_in_new_tab
|
open_in_new_tab
|
||||||
text
|
text
|
||||||
linkConnection {
|
linkConnection {
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ query GetHeader($locale: String!) {
|
|||||||
top_link {
|
top_link {
|
||||||
logged_in {
|
logged_in {
|
||||||
icon
|
icon
|
||||||
|
is_contentstack_link
|
||||||
title
|
title
|
||||||
linkConnection {
|
linkConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -52,9 +53,14 @@ query GetHeader($locale: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logged_out {
|
logged_out {
|
||||||
icon
|
icon
|
||||||
|
is_contentstack_link
|
||||||
title
|
title
|
||||||
linkConnection {
|
linkConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -74,9 +80,14 @@ query GetHeader($locale: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
menu_items {
|
menu_items {
|
||||||
|
is_contentstack_link
|
||||||
title
|
title
|
||||||
linkConnection {
|
linkConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -96,7 +107,12 @@ query GetHeader($locale: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
see_all_link {
|
see_all_link {
|
||||||
|
is_contentstack_link
|
||||||
title
|
title
|
||||||
linkConnection {
|
linkConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -116,10 +132,15 @@ query GetHeader($locale: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
submenu {
|
submenu {
|
||||||
title
|
title
|
||||||
links {
|
links {
|
||||||
|
is_contentstack_link
|
||||||
title
|
title
|
||||||
linkConnection {
|
linkConnection {
|
||||||
edges {
|
edges {
|
||||||
@@ -139,6 +160,10 @@ query GetHeader($locale: String!) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
external_link {
|
||||||
|
href
|
||||||
|
title
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cardConnection {
|
cardConnection {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ import {
|
|||||||
import { Lang } from "@scandic-hotels/common/constants/language"
|
import { Lang } from "@scandic-hotels/common/constants/language"
|
||||||
import { logger } from "@scandic-hotels/common/logger"
|
import { logger } from "@scandic-hotels/common/logger"
|
||||||
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
|
import { removeMultipleSlashes } from "@scandic-hotels/common/utils/url"
|
||||||
|
import {
|
||||||
|
nullableStringUrlValidator,
|
||||||
|
nullableStringValidator,
|
||||||
|
} from "@scandic-hotels/common/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { discriminatedUnion } from "../../../utils/discriminatedUnion"
|
import { discriminatedUnion } from "../../../utils/discriminatedUnion"
|
||||||
import {
|
import {
|
||||||
@@ -97,7 +101,7 @@ export const validateCurrentHeaderConfigSchema = z
|
|||||||
edges: z.array(
|
edges: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
node: z.object({
|
node: z.object({
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().nullish(),
|
||||||
dimension: z.object({
|
dimension: z.object({
|
||||||
height: z.number(),
|
height: z.number(),
|
||||||
width: z.number(),
|
width: z.number(),
|
||||||
@@ -106,8 +110,8 @@ export const validateCurrentHeaderConfigSchema = z
|
|||||||
system: z.object({
|
system: z.object({
|
||||||
uid: z.string(),
|
uid: z.string(),
|
||||||
}),
|
}),
|
||||||
title: z.string().optional().default(""),
|
title: nullableStringValidator,
|
||||||
url: z.string().optional().default(""),
|
url: nullableStringUrlValidator,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
@@ -178,7 +182,7 @@ const validateAppDownload = z.object({
|
|||||||
edges: z.array(
|
edges: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
node: z.object({
|
node: z.object({
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().nullish(),
|
||||||
dimension: z.object({
|
dimension: z.object({
|
||||||
height: z.number(),
|
height: z.number(),
|
||||||
width: z.number(),
|
width: z.number(),
|
||||||
@@ -220,7 +224,7 @@ export const validateCurrentFooterConfigSchema = z.object({
|
|||||||
edges: z.array(
|
edges: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
node: z.object({
|
node: z.object({
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().nullish(),
|
||||||
dimension: z.object({
|
dimension: z.object({
|
||||||
height: z.number(),
|
height: z.number(),
|
||||||
width: z.number(),
|
width: z.number(),
|
||||||
@@ -248,7 +252,7 @@ export const validateCurrentFooterConfigSchema = z.object({
|
|||||||
edges: z.array(
|
edges: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
node: z.object({
|
node: z.object({
|
||||||
description: z.string().optional().nullable(),
|
description: z.string().nullish(),
|
||||||
dimension: z.object({
|
dimension: z.object({
|
||||||
height: z.number(),
|
height: z.number(),
|
||||||
width: z.number(),
|
width: z.number(),
|
||||||
@@ -561,6 +565,51 @@ export const headerRefsSchema = z
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const internalOrExternalLinkSchema = z
|
||||||
|
.object({
|
||||||
|
is_contentstack_link: z.boolean().nullish(),
|
||||||
|
external_link: z
|
||||||
|
.object({
|
||||||
|
href: nullableStringUrlValidator,
|
||||||
|
title: z.string().nullish(),
|
||||||
|
})
|
||||||
|
.nullish(),
|
||||||
|
title: nullableStringValidator,
|
||||||
|
linkConnection: z.object({
|
||||||
|
edges: z.array(
|
||||||
|
z.object({
|
||||||
|
node: linkUnionSchema.transform((data) => {
|
||||||
|
const link = transformPageLink(data)
|
||||||
|
if (link) {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.transform(
|
||||||
|
({ is_contentstack_link, external_link, linkConnection, title }) => {
|
||||||
|
if (is_contentstack_link !== false && linkConnection.edges.length) {
|
||||||
|
const linkRef = linkConnection.edges[0].node
|
||||||
|
return {
|
||||||
|
title: title || linkRef.title,
|
||||||
|
url: linkRef.url,
|
||||||
|
}
|
||||||
|
} else if (is_contentstack_link === false && external_link?.href) {
|
||||||
|
return {
|
||||||
|
title: title || external_link.title || "",
|
||||||
|
url: external_link.href,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const linkSchema = z
|
const linkSchema = z
|
||||||
.object({
|
.object({
|
||||||
linkConnection: z.object({
|
linkConnection: z.object({
|
||||||
@@ -590,7 +639,7 @@ const linkSchema = z
|
|||||||
})
|
})
|
||||||
|
|
||||||
const titleSchema = z.object({
|
const titleSchema = z.object({
|
||||||
title: z.string().optional().default(""),
|
title: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -605,7 +654,7 @@ const linkAndTitleSchema = z.intersection(linkSchema, titleSchema)
|
|||||||
*/
|
*/
|
||||||
export const menuItemSchema = z
|
export const menuItemSchema = z
|
||||||
.intersection(
|
.intersection(
|
||||||
linkAndTitleSchema,
|
internalOrExternalLinkSchema,
|
||||||
z
|
z
|
||||||
.object({
|
.object({
|
||||||
cardConnection: z.object({
|
cardConnection: z.object({
|
||||||
@@ -615,11 +664,11 @@ export const menuItemSchema = z
|
|||||||
})
|
})
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
see_all_link: linkAndTitleSchema,
|
see_all_link: internalOrExternalLinkSchema,
|
||||||
submenu: z.array(
|
submenu: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
links: z.array(linkAndTitleSchema),
|
links: z.array(internalOrExternalLinkSchema),
|
||||||
title: z.string().optional().default(""),
|
title: nullableStringValidator,
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
@@ -636,11 +685,13 @@ export const menuItemSchema = z
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.transform((data) => {
|
.transform(({ title, url, card, seeAllLink, submenu }) => {
|
||||||
return {
|
return {
|
||||||
...data,
|
title,
|
||||||
link: data.submenu.length ? null : data.link,
|
link: submenu.length ? null : { url },
|
||||||
seeAllLink: data.submenu.length ? data.seeAllLink : null,
|
seeAllLink: submenu.length ? seeAllLink : null,
|
||||||
|
card,
|
||||||
|
submenu,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -652,7 +703,7 @@ enum IconName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const topLinkItemSchema = z.intersection(
|
const topLinkItemSchema = z.intersection(
|
||||||
linkAndTitleSchema,
|
internalOrExternalLinkSchema,
|
||||||
z.object({
|
z.object({
|
||||||
icon: z
|
icon: z
|
||||||
.enum(["loyalty", "info", "offer"])
|
.enum(["loyalty", "info", "offer"])
|
||||||
|
|||||||
@@ -15,37 +15,65 @@ export const shortcutsBlockSchema = z.object({
|
|||||||
two_column_list: z.boolean().nullable().default(false),
|
two_column_list: z.boolean().nullable().default(false),
|
||||||
shortcuts: z
|
shortcuts: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z
|
||||||
open_in_new_tab: z.boolean(),
|
.object({
|
||||||
text: z.string().optional().default(""),
|
is_contentstack_link: z.boolean().nullish(),
|
||||||
linkConnection: z.object({
|
external_link: z
|
||||||
edges: z.array(
|
.object({
|
||||||
z.object({
|
href: z.string().nullish().default(""),
|
||||||
node: linkUnionSchema.transform((data) => {
|
title: z.string().nullish(),
|
||||||
const link = transformPageLink(data)
|
|
||||||
if (link) {
|
|
||||||
return link
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}),
|
|
||||||
})
|
})
|
||||||
),
|
.nullish(),
|
||||||
}),
|
open_in_new_tab: z.boolean(),
|
||||||
})
|
text: z.string().optional().default(""),
|
||||||
)
|
linkConnection: z.object({
|
||||||
.transform((data) => {
|
edges: z.array(
|
||||||
return data
|
z.object({
|
||||||
.filter((node) => node.linkConnection.edges.length)
|
node: linkUnionSchema.transform((data) => {
|
||||||
.map((node) => {
|
const link = transformPageLink(data)
|
||||||
const link = node.linkConnection.edges[0].node
|
if (link) {
|
||||||
return {
|
return link
|
||||||
openInNewTab: node.open_in_new_tab,
|
}
|
||||||
text: node.text,
|
return data
|
||||||
title: link.title,
|
}),
|
||||||
url: link.url,
|
})
|
||||||
}
|
),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}),
|
.transform(
|
||||||
|
({
|
||||||
|
is_contentstack_link,
|
||||||
|
external_link,
|
||||||
|
linkConnection,
|
||||||
|
open_in_new_tab,
|
||||||
|
text,
|
||||||
|
}) => {
|
||||||
|
if (
|
||||||
|
is_contentstack_link !== false &&
|
||||||
|
linkConnection.edges.length
|
||||||
|
) {
|
||||||
|
const linkRef = linkConnection.edges[0].node
|
||||||
|
return {
|
||||||
|
openInNewTab: open_in_new_tab,
|
||||||
|
text: text || linkRef.title,
|
||||||
|
url: linkRef.url,
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
is_contentstack_link === false &&
|
||||||
|
external_link?.href
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
openInNewTab: open_in_new_tab,
|
||||||
|
text: text || external_link.title || "",
|
||||||
|
url: external_link.href,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.transform((data) => data.filter((item) => !!item)),
|
||||||
})
|
})
|
||||||
.transform(({ two_column_list, ...rest }) => {
|
.transform(({ two_column_list, ...rest }) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user