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